@agent-native/core 0.133.3 → 0.134.1
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/template-content-developers.mdx +1 -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/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/clips/changelog/2026-07-29-meeting-notes-no-longer-transcribe-the-other-side-twice-when.md +6 -0
- package/corpus/templates/clips/desktop/src/hooks/useMeetingTranscription.ts +37 -48
- package/corpus/templates/clips/desktop/src/lib/transcription-capture.ts +7 -5
- package/corpus/templates/clips/desktop/src/lib/transcription-engine.ts +182 -106
- package/corpus/templates/clips/desktop/src/overlays/live-transcript.tsx +43 -14
- package/corpus/templates/clips/desktop/src/overlays/recording-pill.tsx +3 -3
- package/corpus/templates/clips/desktop/src-tauri/src/echo_guard.rs +443 -0
- package/corpus/templates/clips/desktop/src-tauri/src/lib.rs +1 -0
- package/corpus/templates/clips/desktop/src-tauri/src/whisper_speech.rs +125 -16
- package/corpus/templates/content/.agents/skills/document-editing/references/databases.md +6 -4
- package/corpus/templates/content/actions/_database-membership-lock.ts +25 -0
- package/corpus/templates/content/actions/_database-row-batch.ts +6 -2
- package/corpus/templates/content/actions/_database-source-utils.ts +289 -129
- package/corpus/templates/content/actions/_database-utils.ts +12 -3
- package/corpus/templates/content/actions/add-content-database-source-field-property.ts +39 -21
- package/corpus/templates/content/actions/attach-content-database-source.ts +4 -1
- package/corpus/templates/content/actions/change-content-database-source-role.ts +3 -5
- package/corpus/templates/content/actions/duplicate-document-property.ts +59 -46
- package/corpus/templates/content/actions/execute-builder-source-execution.ts +77 -71
- package/corpus/templates/content/actions/materialize-builder-required-fields.ts +7 -0
- package/corpus/templates/content/actions/remove-database-items.ts +183 -0
- package/corpus/templates/content/actions/set-document-property.ts +57 -39
- package/corpus/templates/content/actions/sync-local-folder-source.ts +7 -0
- package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +471 -329
- package/corpus/templates/content/app/components/editor/database/row-access.ts +45 -0
- package/corpus/templates/content/app/components/editor/database/shared.tsx +141 -38
- package/corpus/templates/content/app/hooks/use-content-database.ts +2 -2
- package/corpus/templates/content/app/i18n-data.ts +130 -0
- package/corpus/templates/content/changelog/2026-07-30-database-rows-can-now-be-removed-without-deleting-their-page.md +6 -0
- package/corpus/templates/content/parity/eval-scenarios.ts +2 -2
- package/corpus/templates/content/parity/matrix.md +1 -1
- package/corpus/templates/content/parity/matrix.ts +4 -5
- package/corpus/templates/content/shared/api.ts +4 -0
- package/corpus/templates/design/.generated/bridge/editor-chrome.generated.ts +26 -11
- package/corpus/templates/design/actions/take-design-screenshot.ts +7 -0
- package/corpus/templates/design/app/components/design/DesignCanvas.tsx +11 -3
- package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +4 -1
- package/corpus/templates/design/app/components/design/bridge/editor-chrome.bridge.ts +54 -17
- package/corpus/templates/design/app/components/design/design-canvas/local-runtime.ts +126 -0
- package/corpus/templates/design/app/components/templates/TemplatePreview.tsx +2 -0
- package/corpus/templates/design/app/pages/Index.tsx +3 -1
- package/corpus/templates/design/app/pages/Present.tsx +2 -1
- package/corpus/templates/design/changelog/2026-07-30-a-broken-inline-script-in-a-generated-screen-is-now-caught-o.md +6 -0
- package/corpus/templates/design/changelog/2026-07-30-design-html-is-now-validated-with-a-spec-grade-html-parser-i.md +6 -0
- package/corpus/templates/design/changelog/2026-07-30-designs-now-load-their-tailwind-and-alpine-runtimes-from-the.md +6 -0
- package/corpus/templates/design/changelog/2026-07-30-designs-with-a-broken-alpine-expression-are-now-caught-on-sa.md +6 -0
- package/corpus/templates/design/changelog/2026-07-30-editing-a-design-no-longer-strips-its-styling-the-canvas-kep.md +6 -0
- package/corpus/templates/design/package.json +4 -0
- package/corpus/templates/design/shared/html-integrity.ts +802 -556
- 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/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 +1 -1
- 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 +3 -3
- 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/progress/routes.d.ts +1 -1
- package/dist/resources/handlers.d.ts +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-engine-api-key-route.d.ts +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/server/transcribe-voice.d.ts +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/template-content-developers.mdx +1 -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/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
- package/corpus/templates/content/actions/delete-database-items.ts +0 -89
|
@@ -17,12 +17,12 @@ declare const _default: import("../../action.js").ActionDefinition<{
|
|
|
17
17
|
id?: undefined;
|
|
18
18
|
provider?: undefined;
|
|
19
19
|
} | {
|
|
20
|
-
error?: undefined;
|
|
21
20
|
configured?: undefined;
|
|
22
21
|
connectPath?: undefined;
|
|
23
22
|
url: string;
|
|
24
23
|
id: string;
|
|
25
24
|
provider: string;
|
|
25
|
+
error?: undefined;
|
|
26
26
|
}>;
|
|
27
27
|
export default _default;
|
|
28
28
|
//# sourceMappingURL=upload-image.d.ts.map
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PostHog LLM-analytics events (`$ai_trace`, `$ai_span`, `$ai_generation`
|
|
3
|
+
* content fields).
|
|
4
|
+
*
|
|
5
|
+
* PostHog models an agent run as a tree: one `$ai_trace` per run, `$ai_span`
|
|
6
|
+
* for non-model work (tool calls), and `$ai_generation` for model round-trips.
|
|
7
|
+
* Every node shares `$ai_trace_id` and links upward through `$ai_parent_id`.
|
|
8
|
+
* Emitting only a generation — which is what this framework did — makes PostHog
|
|
9
|
+
* synthesize a placeholder trace with no tool steps in it.
|
|
10
|
+
*
|
|
11
|
+
* `$ai_session_id` groups traces into a conversation. It is deliberately NOT
|
|
12
|
+
* PostHog's `$session_id`: the latter is the browser session used for session
|
|
13
|
+
* replay, and the two are different lifetimes.
|
|
14
|
+
*
|
|
15
|
+
* Content (`$ai_input` / `$ai_output_choices` / `$ai_input_state` /
|
|
16
|
+
* `$ai_output_state`) is gated on config and always OMITTED when disabled.
|
|
17
|
+
* Sending `[]` instead would be indistinguishable from a run that genuinely had
|
|
18
|
+
* no messages.
|
|
19
|
+
*
|
|
20
|
+
* @see https://posthog.com/docs/ai-observability/traces
|
|
21
|
+
* @see https://posthog.com/docs/ai-observability/spans
|
|
22
|
+
*/
|
|
23
|
+
/** Hard ceiling on serialized content per `$ai_*` field. */
|
|
24
|
+
export declare const MAX_AI_CONTENT_BYTES: number;
|
|
25
|
+
/** Hard ceiling on emitted `$ai_span` events per run. */
|
|
26
|
+
export declare const MAX_AI_SPANS_PER_RUN = 100;
|
|
27
|
+
export interface AiErrorDetail {
|
|
28
|
+
message: string;
|
|
29
|
+
terminal_code?: string;
|
|
30
|
+
terminal_state?: string;
|
|
31
|
+
retryable?: boolean;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Serialize a content value under a byte ceiling.
|
|
35
|
+
*
|
|
36
|
+
* Returns `{ truncated: true }` with a placeholder rather than a silently
|
|
37
|
+
* shortened payload — a trace that shows half a conversation as if it were the
|
|
38
|
+
* whole one is worse than one that says it was cut.
|
|
39
|
+
*/
|
|
40
|
+
export declare function boundAiContent(value: unknown): {
|
|
41
|
+
value: unknown;
|
|
42
|
+
truncated: boolean;
|
|
43
|
+
};
|
|
44
|
+
export interface AiTraceEventInput {
|
|
45
|
+
runId: string;
|
|
46
|
+
threadId: string | null;
|
|
47
|
+
userId: string | null;
|
|
48
|
+
/** Human-readable name for the run, e.g. the agent or thread name. */
|
|
49
|
+
spanName: string;
|
|
50
|
+
model: string;
|
|
51
|
+
provider: string;
|
|
52
|
+
latencySeconds: number;
|
|
53
|
+
isError: boolean;
|
|
54
|
+
error?: AiErrorDetail;
|
|
55
|
+
inputTokens?: number;
|
|
56
|
+
outputTokens?: number;
|
|
57
|
+
costUsd?: number;
|
|
58
|
+
createdAt: number;
|
|
59
|
+
/** Browser session id, when the run originated from a page. Links the trace
|
|
60
|
+
* to PostHog session replay. */
|
|
61
|
+
browserSessionId?: string;
|
|
62
|
+
/** Omitted unless `capturePrompts` is on. */
|
|
63
|
+
inputState?: unknown;
|
|
64
|
+
outputState?: unknown;
|
|
65
|
+
extraProperties?: Record<string, unknown>;
|
|
66
|
+
}
|
|
67
|
+
export declare function emitAiTraceEvent(input: AiTraceEventInput): void;
|
|
68
|
+
export interface AiSpanEventInput {
|
|
69
|
+
runId: string;
|
|
70
|
+
threadId: string | null;
|
|
71
|
+
userId: string | null;
|
|
72
|
+
spanId: string;
|
|
73
|
+
/** Defaults to the run's trace id, which is the tree root. */
|
|
74
|
+
parentId?: string;
|
|
75
|
+
spanName: string;
|
|
76
|
+
latencySeconds: number;
|
|
77
|
+
isError: boolean;
|
|
78
|
+
error?: AiErrorDetail;
|
|
79
|
+
createdAt: number;
|
|
80
|
+
browserSessionId?: string;
|
|
81
|
+
/** Omitted unless `captureToolArgs` / `captureToolResults` are on. */
|
|
82
|
+
inputState?: unknown;
|
|
83
|
+
outputState?: unknown;
|
|
84
|
+
extraProperties?: Record<string, unknown>;
|
|
85
|
+
}
|
|
86
|
+
export declare function emitAiSpanEvent(input: AiSpanEventInput): void;
|
|
87
|
+
export interface AiFeedbackSurveyInput {
|
|
88
|
+
runId: string | null;
|
|
89
|
+
threadId: string | null;
|
|
90
|
+
userId: string | null;
|
|
91
|
+
feedbackType: "thumbs_up" | "thumbs_down" | "category" | "text";
|
|
92
|
+
/** The submitted value: sentiment label, chosen category, or free text. */
|
|
93
|
+
value: string;
|
|
94
|
+
submissionId: string;
|
|
95
|
+
model?: string;
|
|
96
|
+
browserSessionId?: string;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Emit PostHog's documented manual feedback event for an LLM trace.
|
|
100
|
+
*
|
|
101
|
+
* PostHog surfaces feedback in LLM analytics only through `survey sent` keyed
|
|
102
|
+
* to a real survey id — `$ai_feedback` is not a PostHog event and renders
|
|
103
|
+
* nowhere. Returns `false` and emits nothing when no survey id is configured:
|
|
104
|
+
* inventing one would produce events attached to a survey that does not exist.
|
|
105
|
+
*
|
|
106
|
+
* Sent to PostHog ONLY, not through `track()`. The survey response carries the
|
|
107
|
+
* user's free-text feedback verbatim, and configuring a PostHog survey id must
|
|
108
|
+
* not silently start shipping that text to Mixpanel, Amplitude, webhooks, or
|
|
109
|
+
* Agent Native Analytics. Those backends get the content-free `$ai_feedback`
|
|
110
|
+
* event instead.
|
|
111
|
+
*
|
|
112
|
+
* @see https://posthog.com/docs/ai-observability/user-feedback/manual-event-capture
|
|
113
|
+
*/
|
|
114
|
+
export declare function emitAiFeedbackSurveyEvent(input: AiFeedbackSurveyInput): boolean;
|
|
115
|
+
/**
|
|
116
|
+
* Build a structured `$ai_error` from the run's failure information.
|
|
117
|
+
*
|
|
118
|
+
* Returns `undefined` when the run did not fail, so `$ai_error` is absent
|
|
119
|
+
* rather than an empty object on healthy traces.
|
|
120
|
+
*/
|
|
121
|
+
export declare function toAiErrorDetail(errorMessage: string | null | undefined, terminalOutcome?: {
|
|
122
|
+
state?: string;
|
|
123
|
+
code?: string;
|
|
124
|
+
retryable?: boolean;
|
|
125
|
+
}): AiErrorDetail | undefined;
|
|
126
|
+
//# sourceMappingURL=posthog-ai.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"posthog-ai.d.ts","sourceRoot":"","sources":["../../src/observability/posthog-ai.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PostHog LLM-analytics events (`$ai_trace`, `$ai_span`, `$ai_generation`
|
|
3
|
+
* content fields).
|
|
4
|
+
*
|
|
5
|
+
* PostHog models an agent run as a tree: one `$ai_trace` per run, `$ai_span`
|
|
6
|
+
* for non-model work (tool calls), and `$ai_generation` for model round-trips.
|
|
7
|
+
* Every node shares `$ai_trace_id` and links upward through `$ai_parent_id`.
|
|
8
|
+
* Emitting only a generation — which is what this framework did — makes PostHog
|
|
9
|
+
* synthesize a placeholder trace with no tool steps in it.
|
|
10
|
+
*
|
|
11
|
+
* `$ai_session_id` groups traces into a conversation. It is deliberately NOT
|
|
12
|
+
* PostHog's `$session_id`: the latter is the browser session used for session
|
|
13
|
+
* replay, and the two are different lifetimes.
|
|
14
|
+
*
|
|
15
|
+
* Content (`$ai_input` / `$ai_output_choices` / `$ai_input_state` /
|
|
16
|
+
* `$ai_output_state`) is gated on config and always OMITTED when disabled.
|
|
17
|
+
* Sending `[]` instead would be indistinguishable from a run that genuinely had
|
|
18
|
+
* no messages.
|
|
19
|
+
*
|
|
20
|
+
* @see https://posthog.com/docs/ai-observability/traces
|
|
21
|
+
* @see https://posthog.com/docs/ai-observability/spans
|
|
22
|
+
*/
|
|
23
|
+
import { sendPostHogEvent } from "../tracking/providers.js";
|
|
24
|
+
import { boundedText } from "../tracking/redaction.js";
|
|
25
|
+
/** Hard ceiling on serialized content per `$ai_*` field. */
|
|
26
|
+
export const MAX_AI_CONTENT_BYTES = 128 * 1024;
|
|
27
|
+
/** Hard ceiling on emitted `$ai_span` events per run. */
|
|
28
|
+
export const MAX_AI_SPANS_PER_RUN = 100;
|
|
29
|
+
function trackAiEvent(name, properties, userId) {
|
|
30
|
+
for (const key of Object.keys(properties)) {
|
|
31
|
+
if (properties[key] === undefined)
|
|
32
|
+
delete properties[key];
|
|
33
|
+
}
|
|
34
|
+
try {
|
|
35
|
+
void import("../tracking/registry.js")
|
|
36
|
+
.then(({ track }) => {
|
|
37
|
+
track(name, properties, { userId: userId ?? undefined });
|
|
38
|
+
})
|
|
39
|
+
.catch(() => { });
|
|
40
|
+
// coercion-ok: a throw here would break the run it is observing
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
// Tracking must never affect the agent run or trace persistence.
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Serialize a content value under a byte ceiling.
|
|
48
|
+
*
|
|
49
|
+
* Returns `{ truncated: true }` with a placeholder rather than a silently
|
|
50
|
+
* shortened payload — a trace that shows half a conversation as if it were the
|
|
51
|
+
* whole one is worse than one that says it was cut.
|
|
52
|
+
*/
|
|
53
|
+
export function boundAiContent(value) {
|
|
54
|
+
let serialized;
|
|
55
|
+
try {
|
|
56
|
+
serialized = JSON.stringify(value);
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
return { value: "[unserializable]", truncated: true };
|
|
60
|
+
}
|
|
61
|
+
if (serialized === undefined)
|
|
62
|
+
return { value: undefined, truncated: false };
|
|
63
|
+
const bytes = typeof Buffer !== "undefined"
|
|
64
|
+
? Buffer.byteLength(serialized, "utf8")
|
|
65
|
+
: new TextEncoder().encode(serialized).length;
|
|
66
|
+
if (bytes <= MAX_AI_CONTENT_BYTES)
|
|
67
|
+
return { value, truncated: false };
|
|
68
|
+
return {
|
|
69
|
+
value: `[truncated: ${bytes} bytes exceeded the ${MAX_AI_CONTENT_BYTES}-byte trace content limit]`,
|
|
70
|
+
truncated: true,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
export function emitAiTraceEvent(input) {
|
|
74
|
+
const inputContent = input.inputState === undefined
|
|
75
|
+
? undefined
|
|
76
|
+
: boundAiContent(input.inputState);
|
|
77
|
+
const outputContent = input.outputState === undefined
|
|
78
|
+
? undefined
|
|
79
|
+
: boundAiContent(input.outputState);
|
|
80
|
+
trackAiEvent("$ai_trace", {
|
|
81
|
+
...input.extraProperties,
|
|
82
|
+
$ai_trace_id: input.runId,
|
|
83
|
+
$ai_session_id: input.threadId ?? undefined,
|
|
84
|
+
$ai_span_name: input.spanName,
|
|
85
|
+
$ai_model: input.model,
|
|
86
|
+
$ai_provider: input.provider,
|
|
87
|
+
$ai_latency: input.latencySeconds,
|
|
88
|
+
$ai_is_error: input.isError,
|
|
89
|
+
$ai_error: input.error,
|
|
90
|
+
$ai_input_tokens: input.inputTokens,
|
|
91
|
+
$ai_output_tokens: input.outputTokens,
|
|
92
|
+
$ai_total_cost_usd: input.costUsd,
|
|
93
|
+
$ai_input_state: inputContent?.value,
|
|
94
|
+
$ai_output_state: outputContent?.value,
|
|
95
|
+
$ai_input_truncated: inputContent?.truncated || undefined,
|
|
96
|
+
$ai_output_truncated: outputContent?.truncated || undefined,
|
|
97
|
+
$session_id: input.browserSessionId,
|
|
98
|
+
created_at: new Date(input.createdAt).toISOString(),
|
|
99
|
+
}, input.userId);
|
|
100
|
+
}
|
|
101
|
+
export function emitAiSpanEvent(input) {
|
|
102
|
+
const inputContent = input.inputState === undefined
|
|
103
|
+
? undefined
|
|
104
|
+
: boundAiContent(input.inputState);
|
|
105
|
+
const outputContent = input.outputState === undefined
|
|
106
|
+
? undefined
|
|
107
|
+
: boundAiContent(input.outputState);
|
|
108
|
+
trackAiEvent("$ai_span", {
|
|
109
|
+
...input.extraProperties,
|
|
110
|
+
$ai_trace_id: input.runId,
|
|
111
|
+
$ai_session_id: input.threadId ?? undefined,
|
|
112
|
+
$ai_span_id: input.spanId,
|
|
113
|
+
$ai_parent_id: input.parentId ?? input.runId,
|
|
114
|
+
$ai_span_name: input.spanName,
|
|
115
|
+
$ai_latency: input.latencySeconds,
|
|
116
|
+
$ai_is_error: input.isError,
|
|
117
|
+
$ai_error: input.error,
|
|
118
|
+
$ai_input_state: inputContent?.value,
|
|
119
|
+
$ai_output_state: outputContent?.value,
|
|
120
|
+
$ai_input_truncated: inputContent?.truncated || undefined,
|
|
121
|
+
$ai_output_truncated: outputContent?.truncated || undefined,
|
|
122
|
+
$session_id: input.browserSessionId,
|
|
123
|
+
created_at: new Date(input.createdAt).toISOString(),
|
|
124
|
+
}, input.userId);
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Emit PostHog's documented manual feedback event for an LLM trace.
|
|
128
|
+
*
|
|
129
|
+
* PostHog surfaces feedback in LLM analytics only through `survey sent` keyed
|
|
130
|
+
* to a real survey id — `$ai_feedback` is not a PostHog event and renders
|
|
131
|
+
* nowhere. Returns `false` and emits nothing when no survey id is configured:
|
|
132
|
+
* inventing one would produce events attached to a survey that does not exist.
|
|
133
|
+
*
|
|
134
|
+
* Sent to PostHog ONLY, not through `track()`. The survey response carries the
|
|
135
|
+
* user's free-text feedback verbatim, and configuring a PostHog survey id must
|
|
136
|
+
* not silently start shipping that text to Mixpanel, Amplitude, webhooks, or
|
|
137
|
+
* Agent Native Analytics. Those backends get the content-free `$ai_feedback`
|
|
138
|
+
* event instead.
|
|
139
|
+
*
|
|
140
|
+
* @see https://posthog.com/docs/ai-observability/user-feedback/manual-event-capture
|
|
141
|
+
*/
|
|
142
|
+
export function emitAiFeedbackSurveyEvent(input) {
|
|
143
|
+
const surveyId = process.env.POSTHOG_AI_FEEDBACK_SURVEY_ID?.trim();
|
|
144
|
+
if (!surveyId)
|
|
145
|
+
return false;
|
|
146
|
+
const questionId = process.env.POSTHOG_AI_FEEDBACK_SURVEY_QUESTION_ID?.trim();
|
|
147
|
+
// PostHog accepts `$survey_response` for a single-question survey and
|
|
148
|
+
// `$survey_response_<questionId>` when the survey has named questions.
|
|
149
|
+
const responseKey = questionId
|
|
150
|
+
? `$survey_response_${questionId}`
|
|
151
|
+
: "$survey_response";
|
|
152
|
+
const properties = {
|
|
153
|
+
$survey_id: surveyId,
|
|
154
|
+
[responseKey]: input.value,
|
|
155
|
+
$survey_submission_id: input.submissionId,
|
|
156
|
+
$survey_completed: true,
|
|
157
|
+
$ai_trace_id: input.runId ?? undefined,
|
|
158
|
+
$ai_session_id: input.threadId ?? undefined,
|
|
159
|
+
$ai_model: input.model,
|
|
160
|
+
$session_id: input.browserSessionId,
|
|
161
|
+
feedback_type: input.feedbackType,
|
|
162
|
+
source: "agent_observability",
|
|
163
|
+
};
|
|
164
|
+
for (const key of Object.keys(properties)) {
|
|
165
|
+
if (properties[key] === undefined)
|
|
166
|
+
delete properties[key];
|
|
167
|
+
}
|
|
168
|
+
return sendPostHogEvent("survey sent", properties, input.userId ?? "anonymous");
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Build a structured `$ai_error` from the run's failure information.
|
|
172
|
+
*
|
|
173
|
+
* Returns `undefined` when the run did not fail, so `$ai_error` is absent
|
|
174
|
+
* rather than an empty object on healthy traces.
|
|
175
|
+
*/
|
|
176
|
+
export function toAiErrorDetail(errorMessage, terminalOutcome) {
|
|
177
|
+
if (!errorMessage && !terminalOutcome?.code)
|
|
178
|
+
return undefined;
|
|
179
|
+
return {
|
|
180
|
+
message: boundedText(errorMessage ?? terminalOutcome?.code ?? "error", 1000),
|
|
181
|
+
...(terminalOutcome?.state
|
|
182
|
+
? { terminal_state: terminalOutcome.state }
|
|
183
|
+
: {}),
|
|
184
|
+
...(terminalOutcome?.code ? { terminal_code: terminalOutcome.code } : {}),
|
|
185
|
+
...(terminalOutcome?.retryable !== undefined
|
|
186
|
+
? { retryable: terminalOutcome.retryable }
|
|
187
|
+
: {}),
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
//# sourceMappingURL=posthog-ai.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"posthog-ai.js","sourceRoot":"","sources":["../../src/observability/posthog-ai.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * PostHog LLM-analytics events (`$ai_trace`, `$ai_span`, `$ai_generation`\n * content fields).\n *\n * PostHog models an agent run as a tree: one `$ai_trace` per run, `$ai_span`\n * for non-model work (tool calls), and `$ai_generation` for model round-trips.\n * Every node shares `$ai_trace_id` and links upward through `$ai_parent_id`.\n * Emitting only a generation — which is what this framework did — makes PostHog\n * synthesize a placeholder trace with no tool steps in it.\n *\n * `$ai_session_id` groups traces into a conversation. It is deliberately NOT\n * PostHog's `$session_id`: the latter is the browser session used for session\n * replay, and the two are different lifetimes.\n *\n * Content (`$ai_input` / `$ai_output_choices` / `$ai_input_state` /\n * `$ai_output_state`) is gated on config and always OMITTED when disabled.\n * Sending `[]` instead would be indistinguishable from a run that genuinely had\n * no messages.\n *\n * @see https://posthog.com/docs/ai-observability/traces\n * @see https://posthog.com/docs/ai-observability/spans\n */\n\nimport { sendPostHogEvent } from \"../tracking/providers.js\";\nimport { boundedText } from \"../tracking/redaction.js\";\n\n/** Hard ceiling on serialized content per `$ai_*` field. */\nexport const MAX_AI_CONTENT_BYTES = 128 * 1024;\n/** Hard ceiling on emitted `$ai_span` events per run. */\nexport const MAX_AI_SPANS_PER_RUN = 100;\n\nexport interface AiErrorDetail {\n message: string;\n terminal_code?: string;\n terminal_state?: string;\n retryable?: boolean;\n}\n\nfunction trackAiEvent(\n name: string,\n properties: Record<string, unknown>,\n userId: string | null,\n): void {\n for (const key of Object.keys(properties)) {\n if (properties[key] === undefined) delete properties[key];\n }\n try {\n void import(\"../tracking/registry.js\")\n .then(({ track }) => {\n track(name, properties, { userId: userId ?? undefined });\n })\n .catch(() => {});\n // coercion-ok: a throw here would break the run it is observing\n } catch {\n // Tracking must never affect the agent run or trace persistence.\n }\n}\n\n/**\n * Serialize a content value under a byte ceiling.\n *\n * Returns `{ truncated: true }` with a placeholder rather than a silently\n * shortened payload — a trace that shows half a conversation as if it were the\n * whole one is worse than one that says it was cut.\n */\nexport function boundAiContent(value: unknown): {\n value: unknown;\n truncated: boolean;\n} {\n let serialized: string;\n try {\n serialized = JSON.stringify(value);\n } catch {\n return { value: \"[unserializable]\", truncated: true };\n }\n if (serialized === undefined) return { value: undefined, truncated: false };\n\n const bytes =\n typeof Buffer !== \"undefined\"\n ? Buffer.byteLength(serialized, \"utf8\")\n : new TextEncoder().encode(serialized).length;\n if (bytes <= MAX_AI_CONTENT_BYTES) return { value, truncated: false };\n\n return {\n value: `[truncated: ${bytes} bytes exceeded the ${MAX_AI_CONTENT_BYTES}-byte trace content limit]`,\n truncated: true,\n };\n}\n\nexport interface AiTraceEventInput {\n runId: string;\n threadId: string | null;\n userId: string | null;\n /** Human-readable name for the run, e.g. the agent or thread name. */\n spanName: string;\n model: string;\n provider: string;\n latencySeconds: number;\n isError: boolean;\n error?: AiErrorDetail;\n inputTokens?: number;\n outputTokens?: number;\n costUsd?: number;\n createdAt: number;\n /** Browser session id, when the run originated from a page. Links the trace\n * to PostHog session replay. */\n browserSessionId?: string;\n /** Omitted unless `capturePrompts` is on. */\n inputState?: unknown;\n outputState?: unknown;\n extraProperties?: Record<string, unknown>;\n}\n\nexport function emitAiTraceEvent(input: AiTraceEventInput): void {\n const inputContent =\n input.inputState === undefined\n ? undefined\n : boundAiContent(input.inputState);\n const outputContent =\n input.outputState === undefined\n ? undefined\n : boundAiContent(input.outputState);\n\n trackAiEvent(\n \"$ai_trace\",\n {\n ...input.extraProperties,\n $ai_trace_id: input.runId,\n $ai_session_id: input.threadId ?? undefined,\n $ai_span_name: input.spanName,\n $ai_model: input.model,\n $ai_provider: input.provider,\n $ai_latency: input.latencySeconds,\n $ai_is_error: input.isError,\n $ai_error: input.error,\n $ai_input_tokens: input.inputTokens,\n $ai_output_tokens: input.outputTokens,\n $ai_total_cost_usd: input.costUsd,\n $ai_input_state: inputContent?.value,\n $ai_output_state: outputContent?.value,\n $ai_input_truncated: inputContent?.truncated || undefined,\n $ai_output_truncated: outputContent?.truncated || undefined,\n $session_id: input.browserSessionId,\n created_at: new Date(input.createdAt).toISOString(),\n },\n input.userId,\n );\n}\n\nexport interface AiSpanEventInput {\n runId: string;\n threadId: string | null;\n userId: string | null;\n spanId: string;\n /** Defaults to the run's trace id, which is the tree root. */\n parentId?: string;\n spanName: string;\n latencySeconds: number;\n isError: boolean;\n error?: AiErrorDetail;\n createdAt: number;\n browserSessionId?: string;\n /** Omitted unless `captureToolArgs` / `captureToolResults` are on. */\n inputState?: unknown;\n outputState?: unknown;\n extraProperties?: Record<string, unknown>;\n}\n\nexport function emitAiSpanEvent(input: AiSpanEventInput): void {\n const inputContent =\n input.inputState === undefined\n ? undefined\n : boundAiContent(input.inputState);\n const outputContent =\n input.outputState === undefined\n ? undefined\n : boundAiContent(input.outputState);\n\n trackAiEvent(\n \"$ai_span\",\n {\n ...input.extraProperties,\n $ai_trace_id: input.runId,\n $ai_session_id: input.threadId ?? undefined,\n $ai_span_id: input.spanId,\n $ai_parent_id: input.parentId ?? input.runId,\n $ai_span_name: input.spanName,\n $ai_latency: input.latencySeconds,\n $ai_is_error: input.isError,\n $ai_error: input.error,\n $ai_input_state: inputContent?.value,\n $ai_output_state: outputContent?.value,\n $ai_input_truncated: inputContent?.truncated || undefined,\n $ai_output_truncated: outputContent?.truncated || undefined,\n $session_id: input.browserSessionId,\n created_at: new Date(input.createdAt).toISOString(),\n },\n input.userId,\n );\n}\n\nexport interface AiFeedbackSurveyInput {\n runId: string | null;\n threadId: string | null;\n userId: string | null;\n feedbackType: \"thumbs_up\" | \"thumbs_down\" | \"category\" | \"text\";\n /** The submitted value: sentiment label, chosen category, or free text. */\n value: string;\n submissionId: string;\n model?: string;\n browserSessionId?: string;\n}\n\n/**\n * Emit PostHog's documented manual feedback event for an LLM trace.\n *\n * PostHog surfaces feedback in LLM analytics only through `survey sent` keyed\n * to a real survey id — `$ai_feedback` is not a PostHog event and renders\n * nowhere. Returns `false` and emits nothing when no survey id is configured:\n * inventing one would produce events attached to a survey that does not exist.\n *\n * Sent to PostHog ONLY, not through `track()`. The survey response carries the\n * user's free-text feedback verbatim, and configuring a PostHog survey id must\n * not silently start shipping that text to Mixpanel, Amplitude, webhooks, or\n * Agent Native Analytics. Those backends get the content-free `$ai_feedback`\n * event instead.\n *\n * @see https://posthog.com/docs/ai-observability/user-feedback/manual-event-capture\n */\nexport function emitAiFeedbackSurveyEvent(\n input: AiFeedbackSurveyInput,\n): boolean {\n const surveyId = process.env.POSTHOG_AI_FEEDBACK_SURVEY_ID?.trim();\n if (!surveyId) return false;\n\n const questionId = process.env.POSTHOG_AI_FEEDBACK_SURVEY_QUESTION_ID?.trim();\n // PostHog accepts `$survey_response` for a single-question survey and\n // `$survey_response_<questionId>` when the survey has named questions.\n const responseKey = questionId\n ? `$survey_response_${questionId}`\n : \"$survey_response\";\n\n const properties: Record<string, unknown> = {\n $survey_id: surveyId,\n [responseKey]: input.value,\n $survey_submission_id: input.submissionId,\n $survey_completed: true,\n $ai_trace_id: input.runId ?? undefined,\n $ai_session_id: input.threadId ?? undefined,\n $ai_model: input.model,\n $session_id: input.browserSessionId,\n feedback_type: input.feedbackType,\n source: \"agent_observability\",\n };\n for (const key of Object.keys(properties)) {\n if (properties[key] === undefined) delete properties[key];\n }\n\n return sendPostHogEvent(\n \"survey sent\",\n properties,\n input.userId ?? \"anonymous\",\n );\n}\n\n/**\n * Build a structured `$ai_error` from the run's failure information.\n *\n * Returns `undefined` when the run did not fail, so `$ai_error` is absent\n * rather than an empty object on healthy traces.\n */\nexport function toAiErrorDetail(\n errorMessage: string | null | undefined,\n terminalOutcome?: {\n state?: string;\n code?: string;\n retryable?: boolean;\n },\n): AiErrorDetail | undefined {\n if (!errorMessage && !terminalOutcome?.code) return undefined;\n return {\n message: boundedText(\n errorMessage ?? terminalOutcome?.code ?? \"error\",\n 1000,\n ),\n ...(terminalOutcome?.state\n ? { terminal_state: terminalOutcome.state }\n : {}),\n ...(terminalOutcome?.code ? { terminal_code: terminalOutcome.code } : {}),\n ...(terminalOutcome?.retryable !== undefined\n ? { retryable: terminalOutcome.retryable }\n : {}),\n };\n}\n"]}
|
|
@@ -41,16 +41,16 @@ export declare function createObservabilityHandler(): import("h3").EventHandlerW
|
|
|
41
41
|
thumbsUpRate: number;
|
|
42
42
|
avgEvalScore: number;
|
|
43
43
|
} | {
|
|
44
|
-
error?: undefined;
|
|
45
44
|
summary: import("./types.js").TraceSummary;
|
|
46
45
|
spans: import("./types.js").TraceSpan[];
|
|
47
46
|
id?: undefined;
|
|
47
|
+
error?: undefined;
|
|
48
48
|
ok?: undefined;
|
|
49
49
|
} | {
|
|
50
|
-
error?: undefined;
|
|
51
50
|
summary?: undefined;
|
|
52
51
|
spans?: undefined;
|
|
53
52
|
id: string;
|
|
53
|
+
error?: undefined;
|
|
54
54
|
ok?: undefined;
|
|
55
55
|
} | {
|
|
56
56
|
summary?: undefined;
|
|
@@ -59,10 +59,10 @@ export declare function createObservabilityHandler(): import("h3").EventHandlerW
|
|
|
59
59
|
error: any;
|
|
60
60
|
ok?: undefined;
|
|
61
61
|
} | {
|
|
62
|
-
error?: undefined;
|
|
63
62
|
summary?: undefined;
|
|
64
63
|
spans?: undefined;
|
|
65
64
|
id?: undefined;
|
|
65
|
+
error?: undefined;
|
|
66
66
|
ok: boolean;
|
|
67
67
|
}>>;
|
|
68
68
|
//# sourceMappingURL=routes.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../../src/observability/routes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;
|
|
1
|
+
{"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../../src/observability/routes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAqFH,wBAAgB,0BAA0B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IA4VzC"}
|
|
@@ -22,7 +22,9 @@
|
|
|
22
22
|
import { defineEventHandler, getMethod, getQuery, setResponseStatus, } from "h3";
|
|
23
23
|
import { getSession } from "../server/auth.js";
|
|
24
24
|
import { readBody } from "../server/h3-helpers.js";
|
|
25
|
+
import { getRequestContext } from "../server/request-context.js";
|
|
25
26
|
import { track } from "../tracking/registry.js";
|
|
27
|
+
import { emitAiFeedbackSurveyEvent } from "./posthog-ai.js";
|
|
26
28
|
import { getObservabilityOverview, getTraceSummaries, getTraceSummary, getTraceSpansForRun, getEvalsForRun, insertFeedback, getFeedback, getFeedbackStats, getSatisfactionScores, getEvalStats, listExperiments, insertExperiment, getExperiment, updateExperiment, getExperimentResults, } from "./store.js";
|
|
27
29
|
import { trackingIdentityProperties } from "./tracking-identity.js";
|
|
28
30
|
function nanoid(size = 21) {
|
|
@@ -163,11 +165,10 @@ export function createObservabilityHandler() {
|
|
|
163
165
|
userId: owner,
|
|
164
166
|
createdAt: Date.now(),
|
|
165
167
|
});
|
|
166
|
-
|
|
167
|
-
// Category follow-ups intentionally do not emit: a thumbs-down followed
|
|
168
|
-
// by a category would otherwise double-count the same negative signal.
|
|
169
|
-
if (feedbackType === "thumbs_up" || feedbackType === "thumbs_down") {
|
|
168
|
+
{
|
|
170
169
|
const runId = body.runId ? String(body.runId) : null;
|
|
170
|
+
const threadId = body.threadId ? String(body.threadId) : null;
|
|
171
|
+
const isThumb = feedbackType === "thumbs_up" || feedbackType === "thumbs_down";
|
|
171
172
|
let model;
|
|
172
173
|
if (runId) {
|
|
173
174
|
try {
|
|
@@ -179,11 +180,18 @@ export function createObservabilityHandler() {
|
|
|
179
180
|
// best-effort and must never make the submission fail.
|
|
180
181
|
}
|
|
181
182
|
}
|
|
182
|
-
|
|
183
|
+
// Every submission is reported, including `category` and `text`, which
|
|
184
|
+
// previously emitted nothing at all. Only thumbs carry `sentiment` —
|
|
185
|
+
// a category follow-up to a thumbs-down is extra detail about the same
|
|
186
|
+
// vote, so counting it as a second negative would inflate the metric.
|
|
183
187
|
track("$ai_feedback", {
|
|
184
188
|
...trackingIdentityProperties(),
|
|
185
189
|
source: "agent_observability",
|
|
186
|
-
|
|
190
|
+
...(isThumb
|
|
191
|
+
? {
|
|
192
|
+
sentiment: feedbackType === "thumbs_up" ? "positive" : "negative",
|
|
193
|
+
}
|
|
194
|
+
: {}),
|
|
187
195
|
feedback_type: feedbackType,
|
|
188
196
|
run_id: runId,
|
|
189
197
|
thread_id: threadId,
|
|
@@ -192,6 +200,18 @@ export function createObservabilityHandler() {
|
|
|
192
200
|
$ai_session_id: threadId ?? undefined,
|
|
193
201
|
$ai_model: model,
|
|
194
202
|
}, { userId: owner });
|
|
203
|
+
// PostHog shows feedback in LLM analytics only via `survey sent`.
|
|
204
|
+
// No-ops unless a survey id is configured.
|
|
205
|
+
emitAiFeedbackSurveyEvent({
|
|
206
|
+
runId,
|
|
207
|
+
threadId,
|
|
208
|
+
userId: owner,
|
|
209
|
+
feedbackType,
|
|
210
|
+
value: isThumb ? feedbackType : value,
|
|
211
|
+
submissionId: id,
|
|
212
|
+
model,
|
|
213
|
+
browserSessionId: getRequestContext()?.browserSessionId,
|
|
214
|
+
});
|
|
195
215
|
}
|
|
196
216
|
// Fire-and-forget: recompute satisfaction score for the thread.
|
|
197
217
|
if (body.threadId) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"routes.js","sourceRoot":"","sources":["../../src/observability/routes.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * H3 event handlers for the agent observability system.\n *\n * Mounted under `/_agent-native/observability/*` by the observability plugin.\n *\n * GET / — overview stats\n * GET /traces?since=N&limit=N — list trace summaries\n * GET /traces/:runId — get trace detail (spans + summary)\n * GET /traces/:runId/evals — get evals for a run\n * POST /feedback — submit feedback\n * GET /feedback?since=N&limit=N — list feedback entries\n * GET /feedback/stats?since=N — feedback aggregation stats\n * GET /satisfaction?since=N — satisfaction scores\n * GET /evals/stats?since=N — eval stats\n * GET /experiments — list experiments\n * POST /experiments — create experiment\n * GET /experiments/:id — get experiment detail\n * PUT /experiments/:id — update experiment\n * POST /experiments/:id/results — compute experiment results\n * GET /experiments/:id/results — get experiment results\n */\n\nimport {\n defineEventHandler,\n getMethod,\n getQuery,\n setResponseStatus,\n type H3Event,\n} from \"h3\";\n\nimport { getSession } from \"../server/auth.js\";\nimport { readBody } from \"../server/h3-helpers.js\";\nimport { track } from \"../tracking/registry.js\";\nimport {\n getObservabilityOverview,\n getTraceSummaries,\n getTraceSummary,\n getTraceSpansForRun,\n getEvalsForRun,\n insertFeedback,\n getFeedback,\n getFeedbackStats,\n getSatisfactionScores,\n getEvalStats,\n listExperiments,\n insertExperiment,\n getExperiment,\n updateExperiment,\n getExperimentResults,\n} from \"./store.js\";\nimport { trackingIdentityProperties } from \"./tracking-identity.js\";\nimport type { FeedbackType, ExperimentStatus } from \"./types.js\";\n\nfunction nanoid(size = 21): string {\n const alphabet =\n \"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz\";\n let id = \"\";\n const bytes = crypto.getRandomValues(new Uint8Array(size));\n for (let i = 0; i < size; i++) {\n id += alphabet[bytes[i] % alphabet.length];\n }\n return id;\n}\n\nasync function resolveOwner(event: H3Event): Promise<string> {\n const session = await getSession(event).catch(() => null);\n if (!session?.email) {\n const { createError } = await import(\"h3\");\n throw createError({ statusCode: 401, statusMessage: \"Unauthenticated\" });\n }\n return session.email;\n}\n\nfunction canManageExperiments(ownerEmail: string): boolean {\n // Local development keeps the built-in dashboard usable without additional\n // setup. Hosted deployments fail closed unless the operator supplies an\n // explicit allowlist, because experiments affect every user in the app.\n if (process.env.NODE_ENV !== \"production\") return true;\n const admins = (process.env.AGENT_NATIVE_EXPERIMENT_ADMIN_EMAILS ?? \"\")\n .split(\",\")\n .map((email) => email.trim().toLowerCase())\n .filter(Boolean);\n return admins.includes(ownerEmail.trim().toLowerCase());\n}\n\nfunction parseSince(q: Record<string, any>): number {\n const raw = q.since;\n if (typeof raw === \"string\" && raw.length > 0) {\n const n = Number(raw);\n if (!isNaN(n) && n >= 0) return n;\n }\n return Date.now() - 7 * 86_400_000;\n}\n\nfunction parseLimit(q: Record<string, any>, fallback = 100): number {\n const raw = q.limit;\n if (typeof raw === \"string\") {\n const n = Number(raw);\n if (!isNaN(n) && n > 0) return Math.min(n, 500);\n }\n return fallback;\n}\n\nexport function createObservabilityHandler() {\n return defineEventHandler(async (event: H3Event) => {\n const rawMethod = getMethod(event);\n const method = rawMethod === \"HEAD\" ? \"GET\" : rawMethod;\n const pathname = (event.url?.pathname || \"\")\n .replace(/^\\/+/, \"\")\n .replace(/\\/+$/, \"\");\n const parts = pathname ? pathname.split(\"/\") : [];\n\n const owner = await resolveOwner(event);\n\n // Every read endpoint passes `userId: owner` to the store. Omitting\n // it returns rows from every user — load-bearing.\n\n // GET / — overview stats\n if (method === \"GET\" && parts.length === 0) {\n const q = getQuery(event);\n const sinceMs = parseSince(q);\n return getObservabilityOverview(sinceMs, { userId: owner });\n }\n\n // GET /traces — list trace summaries\n if (method === \"GET\" && parts.length === 1 && parts[0] === \"traces\") {\n const q = getQuery(event);\n return getTraceSummaries({\n sinceMs: parseSince(q),\n limit: parseLimit(q),\n userId: owner,\n });\n }\n\n // GET /traces/:runId/evals — evals for a specific run\n if (\n method === \"GET\" &&\n parts.length === 3 &&\n parts[0] === \"traces\" &&\n parts[2] === \"evals\"\n ) {\n return getEvalsForRun(decodeURIComponent(parts[1]), { userId: owner });\n }\n\n // GET /traces/:runId — trace detail (summary + spans). Looking up by\n // runId opens an IDOR vector if we don't ALSO scope to the owner —\n // a user who knows or guesses another user's runId would otherwise\n // get back the trace. The `userId: owner` filter on both lookups\n // returns 404 instead.\n if (method === \"GET\" && parts.length === 2 && parts[0] === \"traces\") {\n const runId = decodeURIComponent(parts[1]);\n const [summary, spans] = await Promise.all([\n getTraceSummary(runId, { userId: owner }),\n getTraceSpansForRun(runId, { userId: owner }),\n ]);\n if (!summary) {\n setResponseStatus(event, 404);\n return { error: \"Trace not found\" };\n }\n return { summary, spans };\n }\n\n // GET /feedback/stats — feedback aggregation stats\n if (\n method === \"GET\" &&\n parts.length === 2 &&\n parts[0] === \"feedback\" &&\n parts[1] === \"stats\"\n ) {\n const q = getQuery(event);\n return getFeedbackStats(parseSince(q), { userId: owner });\n }\n\n // POST /feedback — submit feedback\n if (method === \"POST\" && parts.length === 1 && parts[0] === \"feedback\") {\n let body: any;\n try {\n body = await readBody(event);\n } catch {\n setResponseStatus(event, 400);\n return { error: \"Invalid JSON body\" };\n }\n const feedbackType = body?.feedbackType as FeedbackType | undefined;\n if (\n !feedbackType ||\n ![\"thumbs_up\", \"thumbs_down\", \"category\", \"text\"].includes(feedbackType)\n ) {\n setResponseStatus(event, 400);\n return { error: \"feedbackType is required\" };\n }\n const rawValue = body.value;\n const value =\n rawValue == null\n ? \"\"\n : typeof rawValue === \"object\"\n ? JSON.stringify(rawValue)\n : String(rawValue);\n const id = nanoid();\n await insertFeedback({\n id,\n runId: body.runId ? String(body.runId) : null,\n threadId: body.threadId ? String(body.threadId) : null,\n messageSeq:\n typeof body.messageSeq === \"number\" ? body.messageSeq : null,\n feedbackType,\n value,\n userId: owner,\n createdAt: Date.now(),\n });\n // Emit one content-free analytics event for the explicit thumb itself.\n // Category follow-ups intentionally do not emit: a thumbs-down followed\n // by a category would otherwise double-count the same negative signal.\n if (feedbackType === \"thumbs_up\" || feedbackType === \"thumbs_down\") {\n const runId = body.runId ? String(body.runId) : null;\n let model: string | undefined;\n if (runId) {\n try {\n const summary = await getTraceSummary(runId, { userId: owner });\n model = summary?.model || undefined;\n } catch {\n // Feedback persistence is authoritative; analytics enrichment is\n // best-effort and must never make the submission fail.\n }\n }\n\n const threadId = body.threadId ? String(body.threadId) : null;\n track(\n \"$ai_feedback\",\n {\n ...trackingIdentityProperties(),\n source: \"agent_observability\",\n sentiment: feedbackType === \"thumbs_up\" ? \"positive\" : \"negative\",\n feedback_type: feedbackType,\n run_id: runId,\n thread_id: threadId,\n model,\n $ai_trace_id: runId ?? undefined,\n $ai_session_id: threadId ?? undefined,\n $ai_model: model,\n },\n { userId: owner },\n );\n }\n // Fire-and-forget: recompute satisfaction score for the thread.\n if (body.threadId) {\n import(\"./feedback.js\")\n .then(({ computeSatisfactionScore }) =>\n computeSatisfactionScore(String(body.threadId), {\n userId: owner,\n }).catch(() => {}),\n )\n .catch(() => {});\n }\n return { id };\n }\n\n // GET /feedback — list feedback entries\n if (method === \"GET\" && parts.length === 1 && parts[0] === \"feedback\") {\n const q = getQuery(event);\n return getFeedback({\n sinceMs: parseSince(q),\n limit: parseLimit(q),\n userId: owner,\n });\n }\n\n // GET /satisfaction — satisfaction scores\n if (method === \"GET\" && parts.length === 1 && parts[0] === \"satisfaction\") {\n const q = getQuery(event);\n return getSatisfactionScores({\n sinceMs: parseSince(q),\n userId: owner,\n });\n }\n\n // GET /evals/stats — eval stats\n if (\n method === \"GET\" &&\n parts.length === 2 &&\n parts[0] === \"evals\" &&\n parts[1] === \"stats\"\n ) {\n const q = getQuery(event);\n return getEvalStats(parseSince(q), { userId: owner });\n }\n\n if (parts[0] === \"experiments\" && !canManageExperiments(owner)) {\n setResponseStatus(event, 403);\n return { error: \"Experiment administrator access required\" };\n }\n\n // POST /experiments — create experiment. Records the calling user as\n // the owner so subsequent PUT / POST results require the same caller.\n if (method === \"POST\" && parts.length === 1 && parts[0] === \"experiments\") {\n let body: any;\n try {\n body = await readBody(event);\n } catch {\n setResponseStatus(event, 400);\n return { error: \"Invalid JSON body\" };\n }\n if (!body?.name) {\n setResponseStatus(event, 400);\n return { error: \"name is required\" };\n }\n if (body.variants !== undefined && !Array.isArray(body.variants)) {\n setResponseStatus(event, 400);\n return { error: \"variants must be an array\" };\n }\n const id = nanoid();\n await insertExperiment({\n id,\n name: String(body.name),\n status: \"draft\",\n variants: Array.isArray(body.variants) ? body.variants : [],\n metrics: Array.isArray(body.metrics) ? body.metrics : [],\n assignmentLevel:\n body.assignmentLevel === \"session\" ? \"session\" : \"user\",\n startedAt: null,\n endedAt: null,\n createdAt: Date.now(),\n ownerEmail: owner,\n });\n return { id };\n }\n\n // Experiments are platform-wide A/B test configurations — they assign\n // variants across all users, so reads are NOT per-user scoped. Writes\n // are gated by authentication above (only authenticated users or\n // local-dev can reach this point).\n\n // GET /experiments — list experiments\n if (method === \"GET\" && parts.length === 1 && parts[0] === \"experiments\") {\n return listExperiments();\n }\n\n // POST /experiments/:id/results — compute experiment results. Only\n // the experiment's owner may trigger a recomputation in a multi-tenant\n // deployment; legacy rows (no owner) fall through to the\n // authenticated-only gate above.\n if (\n method === \"POST\" &&\n parts.length === 3 &&\n parts[0] === \"experiments\" &&\n parts[2] === \"results\"\n ) {\n const id = decodeURIComponent(parts[1]);\n const existing = await getExperiment(id);\n if (!existing) {\n setResponseStatus(event, 404);\n return { error: \"Experiment not found\" };\n }\n if (existing.ownerEmail && existing.ownerEmail !== owner) {\n setResponseStatus(event, 404);\n return { error: \"Experiment not found\" };\n }\n try {\n const { computeExperimentResults } = await import(\"./experiments.js\");\n const results = await computeExperimentResults(id);\n return results;\n } catch (err: any) {\n setResponseStatus(event, 500);\n return { error: err?.message ?? \"Failed to compute results\" };\n }\n }\n\n // GET /experiments/:id/results — experiment results\n if (\n method === \"GET\" &&\n parts.length === 3 &&\n parts[0] === \"experiments\" &&\n parts[2] === \"results\"\n ) {\n return getExperimentResults(decodeURIComponent(parts[1]));\n }\n\n // PUT /experiments/:id — update experiment. Restricted to the\n // experiment owner; cross-user mutation would let one signed-in user\n // silently end / reshape another user's experiment (variant\n // assignments, status, metrics). Legacy rows without an owner remain\n // updatable by any authenticated user — they're treated as\n // platform-wide and operators should re-save them to lock down ownership.\n if (method === \"PUT\" && parts.length === 2 && parts[0] === \"experiments\") {\n const id = decodeURIComponent(parts[1]);\n const existing = await getExperiment(id);\n if (!existing) {\n setResponseStatus(event, 404);\n return { error: \"Experiment not found\" };\n }\n if (existing.ownerEmail && existing.ownerEmail !== owner) {\n setResponseStatus(event, 404);\n return { error: \"Experiment not found\" };\n }\n let body: any;\n try {\n body = await readBody(event);\n } catch {\n setResponseStatus(event, 400);\n return { error: \"Invalid JSON body\" };\n }\n const updates: Record<string, any> = {};\n if (typeof body.name === \"string\") updates.name = body.name;\n if (typeof body.status === \"string\") {\n const s = body.status as ExperimentStatus;\n if (![\"draft\", \"running\", \"paused\", \"completed\"].includes(s)) {\n setResponseStatus(event, 400);\n return { error: \"Invalid status\" };\n }\n updates.status = s;\n if (s === \"completed\") updates.endedAt = Date.now();\n }\n if (Array.isArray(body.variants)) updates.variants = body.variants;\n if (Array.isArray(body.metrics)) updates.metrics = body.metrics;\n await updateExperiment(id, updates);\n return { ok: true };\n }\n\n // GET /experiments/:id — experiment detail\n if (method === \"GET\" && parts.length === 2 && parts[0] === \"experiments\") {\n const exp = await getExperiment(decodeURIComponent(parts[1]));\n if (!exp) {\n setResponseStatus(event, 404);\n return { error: \"Experiment not found\" };\n }\n return exp;\n }\n\n setResponseStatus(event, 404);\n return { error: \"Not found\" };\n });\n}\n"]}
|
|
1
|
+
{"version":3,"file":"routes.js","sourceRoot":"","sources":["../../src/observability/routes.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * H3 event handlers for the agent observability system.\n *\n * Mounted under `/_agent-native/observability/*` by the observability plugin.\n *\n * GET / — overview stats\n * GET /traces?since=N&limit=N — list trace summaries\n * GET /traces/:runId — get trace detail (spans + summary)\n * GET /traces/:runId/evals — get evals for a run\n * POST /feedback — submit feedback\n * GET /feedback?since=N&limit=N — list feedback entries\n * GET /feedback/stats?since=N — feedback aggregation stats\n * GET /satisfaction?since=N — satisfaction scores\n * GET /evals/stats?since=N — eval stats\n * GET /experiments — list experiments\n * POST /experiments — create experiment\n * GET /experiments/:id — get experiment detail\n * PUT /experiments/:id — update experiment\n * POST /experiments/:id/results — compute experiment results\n * GET /experiments/:id/results — get experiment results\n */\n\nimport {\n defineEventHandler,\n getMethod,\n getQuery,\n setResponseStatus,\n type H3Event,\n} from \"h3\";\n\nimport { getSession } from \"../server/auth.js\";\nimport { readBody } from \"../server/h3-helpers.js\";\nimport { getRequestContext } from \"../server/request-context.js\";\nimport { track } from \"../tracking/registry.js\";\nimport { emitAiFeedbackSurveyEvent } from \"./posthog-ai.js\";\nimport {\n getObservabilityOverview,\n getTraceSummaries,\n getTraceSummary,\n getTraceSpansForRun,\n getEvalsForRun,\n insertFeedback,\n getFeedback,\n getFeedbackStats,\n getSatisfactionScores,\n getEvalStats,\n listExperiments,\n insertExperiment,\n getExperiment,\n updateExperiment,\n getExperimentResults,\n} from \"./store.js\";\nimport { trackingIdentityProperties } from \"./tracking-identity.js\";\nimport type { FeedbackType, ExperimentStatus } from \"./types.js\";\n\nfunction nanoid(size = 21): string {\n const alphabet =\n \"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz\";\n let id = \"\";\n const bytes = crypto.getRandomValues(new Uint8Array(size));\n for (let i = 0; i < size; i++) {\n id += alphabet[bytes[i] % alphabet.length];\n }\n return id;\n}\n\nasync function resolveOwner(event: H3Event): Promise<string> {\n const session = await getSession(event).catch(() => null);\n if (!session?.email) {\n const { createError } = await import(\"h3\");\n throw createError({ statusCode: 401, statusMessage: \"Unauthenticated\" });\n }\n return session.email;\n}\n\nfunction canManageExperiments(ownerEmail: string): boolean {\n // Local development keeps the built-in dashboard usable without additional\n // setup. Hosted deployments fail closed unless the operator supplies an\n // explicit allowlist, because experiments affect every user in the app.\n if (process.env.NODE_ENV !== \"production\") return true;\n const admins = (process.env.AGENT_NATIVE_EXPERIMENT_ADMIN_EMAILS ?? \"\")\n .split(\",\")\n .map((email) => email.trim().toLowerCase())\n .filter(Boolean);\n return admins.includes(ownerEmail.trim().toLowerCase());\n}\n\nfunction parseSince(q: Record<string, any>): number {\n const raw = q.since;\n if (typeof raw === \"string\" && raw.length > 0) {\n const n = Number(raw);\n if (!isNaN(n) && n >= 0) return n;\n }\n return Date.now() - 7 * 86_400_000;\n}\n\nfunction parseLimit(q: Record<string, any>, fallback = 100): number {\n const raw = q.limit;\n if (typeof raw === \"string\") {\n const n = Number(raw);\n if (!isNaN(n) && n > 0) return Math.min(n, 500);\n }\n return fallback;\n}\n\nexport function createObservabilityHandler() {\n return defineEventHandler(async (event: H3Event) => {\n const rawMethod = getMethod(event);\n const method = rawMethod === \"HEAD\" ? \"GET\" : rawMethod;\n const pathname = (event.url?.pathname || \"\")\n .replace(/^\\/+/, \"\")\n .replace(/\\/+$/, \"\");\n const parts = pathname ? pathname.split(\"/\") : [];\n\n const owner = await resolveOwner(event);\n\n // Every read endpoint passes `userId: owner` to the store. Omitting\n // it returns rows from every user — load-bearing.\n\n // GET / — overview stats\n if (method === \"GET\" && parts.length === 0) {\n const q = getQuery(event);\n const sinceMs = parseSince(q);\n return getObservabilityOverview(sinceMs, { userId: owner });\n }\n\n // GET /traces — list trace summaries\n if (method === \"GET\" && parts.length === 1 && parts[0] === \"traces\") {\n const q = getQuery(event);\n return getTraceSummaries({\n sinceMs: parseSince(q),\n limit: parseLimit(q),\n userId: owner,\n });\n }\n\n // GET /traces/:runId/evals — evals for a specific run\n if (\n method === \"GET\" &&\n parts.length === 3 &&\n parts[0] === \"traces\" &&\n parts[2] === \"evals\"\n ) {\n return getEvalsForRun(decodeURIComponent(parts[1]), { userId: owner });\n }\n\n // GET /traces/:runId — trace detail (summary + spans). Looking up by\n // runId opens an IDOR vector if we don't ALSO scope to the owner —\n // a user who knows or guesses another user's runId would otherwise\n // get back the trace. The `userId: owner` filter on both lookups\n // returns 404 instead.\n if (method === \"GET\" && parts.length === 2 && parts[0] === \"traces\") {\n const runId = decodeURIComponent(parts[1]);\n const [summary, spans] = await Promise.all([\n getTraceSummary(runId, { userId: owner }),\n getTraceSpansForRun(runId, { userId: owner }),\n ]);\n if (!summary) {\n setResponseStatus(event, 404);\n return { error: \"Trace not found\" };\n }\n return { summary, spans };\n }\n\n // GET /feedback/stats — feedback aggregation stats\n if (\n method === \"GET\" &&\n parts.length === 2 &&\n parts[0] === \"feedback\" &&\n parts[1] === \"stats\"\n ) {\n const q = getQuery(event);\n return getFeedbackStats(parseSince(q), { userId: owner });\n }\n\n // POST /feedback — submit feedback\n if (method === \"POST\" && parts.length === 1 && parts[0] === \"feedback\") {\n let body: any;\n try {\n body = await readBody(event);\n } catch {\n setResponseStatus(event, 400);\n return { error: \"Invalid JSON body\" };\n }\n const feedbackType = body?.feedbackType as FeedbackType | undefined;\n if (\n !feedbackType ||\n ![\"thumbs_up\", \"thumbs_down\", \"category\", \"text\"].includes(feedbackType)\n ) {\n setResponseStatus(event, 400);\n return { error: \"feedbackType is required\" };\n }\n const rawValue = body.value;\n const value =\n rawValue == null\n ? \"\"\n : typeof rawValue === \"object\"\n ? JSON.stringify(rawValue)\n : String(rawValue);\n const id = nanoid();\n await insertFeedback({\n id,\n runId: body.runId ? String(body.runId) : null,\n threadId: body.threadId ? String(body.threadId) : null,\n messageSeq:\n typeof body.messageSeq === \"number\" ? body.messageSeq : null,\n feedbackType,\n value,\n userId: owner,\n createdAt: Date.now(),\n });\n {\n const runId = body.runId ? String(body.runId) : null;\n const threadId = body.threadId ? String(body.threadId) : null;\n const isThumb =\n feedbackType === \"thumbs_up\" || feedbackType === \"thumbs_down\";\n let model: string | undefined;\n if (runId) {\n try {\n const summary = await getTraceSummary(runId, { userId: owner });\n model = summary?.model || undefined;\n } catch {\n // Feedback persistence is authoritative; analytics enrichment is\n // best-effort and must never make the submission fail.\n }\n }\n\n // Every submission is reported, including `category` and `text`, which\n // previously emitted nothing at all. Only thumbs carry `sentiment` —\n // a category follow-up to a thumbs-down is extra detail about the same\n // vote, so counting it as a second negative would inflate the metric.\n track(\n \"$ai_feedback\",\n {\n ...trackingIdentityProperties(),\n source: \"agent_observability\",\n ...(isThumb\n ? {\n sentiment:\n feedbackType === \"thumbs_up\" ? \"positive\" : \"negative\",\n }\n : {}),\n feedback_type: feedbackType,\n run_id: runId,\n thread_id: threadId,\n model,\n $ai_trace_id: runId ?? undefined,\n $ai_session_id: threadId ?? undefined,\n $ai_model: model,\n },\n { userId: owner },\n );\n\n // PostHog shows feedback in LLM analytics only via `survey sent`.\n // No-ops unless a survey id is configured.\n emitAiFeedbackSurveyEvent({\n runId,\n threadId,\n userId: owner,\n feedbackType,\n value: isThumb ? feedbackType : value,\n submissionId: id,\n model,\n browserSessionId: getRequestContext()?.browserSessionId,\n });\n }\n // Fire-and-forget: recompute satisfaction score for the thread.\n if (body.threadId) {\n import(\"./feedback.js\")\n .then(({ computeSatisfactionScore }) =>\n computeSatisfactionScore(String(body.threadId), {\n userId: owner,\n }).catch(() => {}),\n )\n .catch(() => {});\n }\n return { id };\n }\n\n // GET /feedback — list feedback entries\n if (method === \"GET\" && parts.length === 1 && parts[0] === \"feedback\") {\n const q = getQuery(event);\n return getFeedback({\n sinceMs: parseSince(q),\n limit: parseLimit(q),\n userId: owner,\n });\n }\n\n // GET /satisfaction — satisfaction scores\n if (method === \"GET\" && parts.length === 1 && parts[0] === \"satisfaction\") {\n const q = getQuery(event);\n return getSatisfactionScores({\n sinceMs: parseSince(q),\n userId: owner,\n });\n }\n\n // GET /evals/stats — eval stats\n if (\n method === \"GET\" &&\n parts.length === 2 &&\n parts[0] === \"evals\" &&\n parts[1] === \"stats\"\n ) {\n const q = getQuery(event);\n return getEvalStats(parseSince(q), { userId: owner });\n }\n\n if (parts[0] === \"experiments\" && !canManageExperiments(owner)) {\n setResponseStatus(event, 403);\n return { error: \"Experiment administrator access required\" };\n }\n\n // POST /experiments — create experiment. Records the calling user as\n // the owner so subsequent PUT / POST results require the same caller.\n if (method === \"POST\" && parts.length === 1 && parts[0] === \"experiments\") {\n let body: any;\n try {\n body = await readBody(event);\n } catch {\n setResponseStatus(event, 400);\n return { error: \"Invalid JSON body\" };\n }\n if (!body?.name) {\n setResponseStatus(event, 400);\n return { error: \"name is required\" };\n }\n if (body.variants !== undefined && !Array.isArray(body.variants)) {\n setResponseStatus(event, 400);\n return { error: \"variants must be an array\" };\n }\n const id = nanoid();\n await insertExperiment({\n id,\n name: String(body.name),\n status: \"draft\",\n variants: Array.isArray(body.variants) ? body.variants : [],\n metrics: Array.isArray(body.metrics) ? body.metrics : [],\n assignmentLevel:\n body.assignmentLevel === \"session\" ? \"session\" : \"user\",\n startedAt: null,\n endedAt: null,\n createdAt: Date.now(),\n ownerEmail: owner,\n });\n return { id };\n }\n\n // Experiments are platform-wide A/B test configurations — they assign\n // variants across all users, so reads are NOT per-user scoped. Writes\n // are gated by authentication above (only authenticated users or\n // local-dev can reach this point).\n\n // GET /experiments — list experiments\n if (method === \"GET\" && parts.length === 1 && parts[0] === \"experiments\") {\n return listExperiments();\n }\n\n // POST /experiments/:id/results — compute experiment results. Only\n // the experiment's owner may trigger a recomputation in a multi-tenant\n // deployment; legacy rows (no owner) fall through to the\n // authenticated-only gate above.\n if (\n method === \"POST\" &&\n parts.length === 3 &&\n parts[0] === \"experiments\" &&\n parts[2] === \"results\"\n ) {\n const id = decodeURIComponent(parts[1]);\n const existing = await getExperiment(id);\n if (!existing) {\n setResponseStatus(event, 404);\n return { error: \"Experiment not found\" };\n }\n if (existing.ownerEmail && existing.ownerEmail !== owner) {\n setResponseStatus(event, 404);\n return { error: \"Experiment not found\" };\n }\n try {\n const { computeExperimentResults } = await import(\"./experiments.js\");\n const results = await computeExperimentResults(id);\n return results;\n } catch (err: any) {\n setResponseStatus(event, 500);\n return { error: err?.message ?? \"Failed to compute results\" };\n }\n }\n\n // GET /experiments/:id/results — experiment results\n if (\n method === \"GET\" &&\n parts.length === 3 &&\n parts[0] === \"experiments\" &&\n parts[2] === \"results\"\n ) {\n return getExperimentResults(decodeURIComponent(parts[1]));\n }\n\n // PUT /experiments/:id — update experiment. Restricted to the\n // experiment owner; cross-user mutation would let one signed-in user\n // silently end / reshape another user's experiment (variant\n // assignments, status, metrics). Legacy rows without an owner remain\n // updatable by any authenticated user — they're treated as\n // platform-wide and operators should re-save them to lock down ownership.\n if (method === \"PUT\" && parts.length === 2 && parts[0] === \"experiments\") {\n const id = decodeURIComponent(parts[1]);\n const existing = await getExperiment(id);\n if (!existing) {\n setResponseStatus(event, 404);\n return { error: \"Experiment not found\" };\n }\n if (existing.ownerEmail && existing.ownerEmail !== owner) {\n setResponseStatus(event, 404);\n return { error: \"Experiment not found\" };\n }\n let body: any;\n try {\n body = await readBody(event);\n } catch {\n setResponseStatus(event, 400);\n return { error: \"Invalid JSON body\" };\n }\n const updates: Record<string, any> = {};\n if (typeof body.name === \"string\") updates.name = body.name;\n if (typeof body.status === \"string\") {\n const s = body.status as ExperimentStatus;\n if (![\"draft\", \"running\", \"paused\", \"completed\"].includes(s)) {\n setResponseStatus(event, 400);\n return { error: \"Invalid status\" };\n }\n updates.status = s;\n if (s === \"completed\") updates.endedAt = Date.now();\n }\n if (Array.isArray(body.variants)) updates.variants = body.variants;\n if (Array.isArray(body.metrics)) updates.metrics = body.metrics;\n await updateExperiment(id, updates);\n return { ok: true };\n }\n\n // GET /experiments/:id — experiment detail\n if (method === \"GET\" && parts.length === 2 && parts[0] === \"experiments\") {\n const exp = await getExperiment(decodeURIComponent(parts[1]));\n if (!exp) {\n setResponseStatus(event, 404);\n return { error: \"Experiment not found\" };\n }\n return exp;\n }\n\n setResponseStatus(event, 404);\n return { error: \"Not found\" };\n });\n}\n"]}
|