@agent-native/core 0.133.2 → 0.134.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/corpus/README.md +2 -2
- package/corpus/core/CHANGELOG.md +62 -0
- package/corpus/core/docs/content/observability.mdx +41 -1
- package/corpus/core/docs/content/tracking.mdx +25 -0
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/agent/run-manager.ts +7 -0
- package/corpus/core/src/client/agent-chat-adapter.ts +10 -0
- package/corpus/core/src/client/analytics.ts +105 -1
- package/corpus/core/src/client/use-action.ts +6 -0
- package/corpus/core/src/deploy/build.ts +38 -1
- package/corpus/core/src/observability/posthog-ai.ts +294 -0
- package/corpus/core/src/observability/routes.ts +29 -6
- package/corpus/core/src/observability/traces.ts +287 -12
- package/corpus/core/src/observability/types.ts +11 -0
- package/corpus/core/src/secrets/register-framework-secrets.ts +16 -0
- package/corpus/core/src/server/agent-run-context.ts +21 -0
- package/corpus/core/src/server/capture-error.ts +8 -0
- package/corpus/core/src/server/core-routes-plugin.ts +75 -4
- package/corpus/core/src/server/deploy-environment.ts +57 -0
- package/corpus/core/src/server/error-noise-filter.ts +268 -0
- package/corpus/core/src/server/posthog-config.ts +75 -0
- package/corpus/core/src/server/request-context.ts +6 -0
- package/corpus/core/src/server/sentry-config.ts +4 -8
- package/corpus/core/src/server/sentry-plugin.ts +13 -29
- package/corpus/core/src/server/sentry.ts +13 -156
- package/corpus/core/src/server/ssr-handler.ts +6 -1
- package/corpus/core/src/shared/mcp-embed-headers.ts +1 -1
- package/corpus/core/src/templates/workspace-core/.agents/skills/observability/SKILL.md +36 -10
- package/corpus/core/src/templates/workspace-core/.agents/skills/tracking/SKILL.md +55 -1
- package/corpus/core/src/tracking/error-capture.ts +44 -109
- package/corpus/core/src/tracking/index.ts +11 -0
- package/corpus/core/src/tracking/posthog-exception.ts +305 -0
- package/corpus/core/src/tracking/providers.ts +91 -14
- package/corpus/core/src/tracking/redaction.ts +105 -0
- package/corpus/templates/content/actions/_batch-utils.ts +33 -0
- package/corpus/templates/content/actions/_builder-cms-read-client.ts +173 -81
- package/corpus/templates/content/actions/_content-files.ts +31 -21
- package/corpus/templates/content/actions/_database-source-utils.ts +653 -155
- package/corpus/templates/content/actions/_database-utils.ts +535 -41
- package/corpus/templates/content/actions/add-content-database-source-field-property.ts +291 -240
- package/corpus/templates/content/actions/add-database-item.ts +15 -1
- package/corpus/templates/content/actions/attach-content-database-source.ts +156 -58
- package/corpus/templates/content/actions/bind-content-database-source-field.ts +2 -2
- package/corpus/templates/content/actions/cancel-prepared-builder-source-update.ts +4 -1
- package/corpus/templates/content/actions/delete-database-items.ts +8 -2
- package/corpus/templates/content/actions/disconnect-content-database-source.ts +2 -2
- package/corpus/templates/content/actions/duplicate-database-item.ts +15 -1
- package/corpus/templates/content/actions/duplicate-database-items.ts +19 -1
- package/corpus/templates/content/actions/execute-builder-source-execution.ts +2 -1
- package/corpus/templates/content/actions/get-content-database.ts +12 -64
- package/corpus/templates/content/actions/move-database-item.ts +4 -1
- package/corpus/templates/content/actions/prepare-builder-source-execution.ts +1 -1
- package/corpus/templates/content/actions/prepare-builder-source-review.ts +4 -8
- package/corpus/templates/content/actions/preview-content-database-source-attach.ts +92 -0
- package/corpus/templates/content/actions/process-builder-body-hydration.ts +2 -1
- package/corpus/templates/content/actions/query-content-database-items.ts +64 -0
- package/corpus/templates/content/actions/refresh-content-database-source.ts +7 -0
- package/corpus/templates/content/actions/review-content-database-source-change-set.ts +1 -1
- package/corpus/templates/content/actions/set-content-database-source-write-mode.ts +1 -1
- package/corpus/templates/content/actions/stage-builder-revision.ts +1 -1
- package/corpus/templates/content/actions/update-content-database-personal-view.ts +29 -3
- package/corpus/templates/content/actions/update-content-database-view.ts +1 -1
- package/corpus/templates/content/actions/validate-builder-source-execution.ts +1 -1
- package/corpus/templates/content/app/components/editor/DocumentProperties.tsx +3 -22
- package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +267 -105
- package/corpus/templates/content/app/components/sidebar/DocumentSidebar.tsx +82 -1
- package/corpus/templates/content/app/hooks/use-content-database.ts +285 -24
- package/corpus/templates/content/app/hooks/use-document-properties.ts +7 -6
- package/corpus/templates/content/app/i18n-data.ts +10 -0
- package/corpus/templates/content/changelog/2026-07-29-large-databases-keep-useful-rows-visible.md +6 -0
- package/corpus/templates/content/changelog/2026-07-30-builder-source-columns-now-appear-immediately-when-connected.md +6 -0
- package/corpus/templates/content/changelog/2026-07-30-large-builder-backed-tables-show-useful-rows-sooner-and-fini.md +6 -0
- package/corpus/templates/content/changelog/2026-08-01-large-builder-databases-now-show-rows-immediately-and-finish.md +6 -0
- package/corpus/templates/content/parity/matrix.md +2 -1
- package/corpus/templates/content/parity/matrix.ts +25 -0
- package/corpus/templates/content/shared/api.ts +54 -4
- package/corpus/templates/content/shared/database-query.ts +359 -0
- package/dist/agent/run-manager.d.ts.map +1 -1
- package/dist/agent/run-manager.js +7 -0
- package/dist/agent/run-manager.js.map +1 -1
- package/dist/client/agent-chat-adapter.d.ts.map +1 -1
- package/dist/client/agent-chat-adapter.js +12 -0
- package/dist/client/agent-chat-adapter.js.map +1 -1
- package/dist/client/analytics.d.ts +8 -0
- package/dist/client/analytics.d.ts.map +1 -1
- package/dist/client/analytics.js +95 -1
- package/dist/client/analytics.js.map +1 -1
- package/dist/client/use-action.d.ts.map +1 -1
- package/dist/client/use-action.js +6 -0
- package/dist/client/use-action.js.map +1 -1
- package/dist/collab/routes.d.ts +1 -1
- package/dist/collab/struct-routes.d.ts +1 -1
- package/dist/deploy/build.d.ts.map +1 -1
- package/dist/deploy/build.js +38 -1
- package/dist/deploy/build.js.map +1 -1
- package/dist/file-upload/actions/upload-image.d.ts +1 -1
- package/dist/notifications/routes.d.ts +3 -3
- package/dist/observability/posthog-ai.d.ts +126 -0
- package/dist/observability/posthog-ai.d.ts.map +1 -0
- package/dist/observability/posthog-ai.js +190 -0
- package/dist/observability/posthog-ai.js.map +1 -0
- package/dist/observability/routes.d.ts +1 -1
- package/dist/observability/routes.d.ts.map +1 -1
- package/dist/observability/routes.js +26 -6
- package/dist/observability/routes.js.map +1 -1
- package/dist/observability/traces.d.ts +9 -0
- package/dist/observability/traces.d.ts.map +1 -1
- package/dist/observability/traces.js +219 -10
- package/dist/observability/traces.js.map +1 -1
- package/dist/observability/types.d.ts +10 -0
- package/dist/observability/types.d.ts.map +1 -1
- package/dist/observability/types.js +1 -0
- package/dist/observability/types.js.map +1 -1
- package/dist/secrets/register-framework-secrets.d.ts.map +1 -1
- package/dist/secrets/register-framework-secrets.js +14 -0
- package/dist/secrets/register-framework-secrets.js.map +1 -1
- package/dist/server/agent-run-context.d.ts +8 -0
- package/dist/server/agent-run-context.d.ts.map +1 -1
- package/dist/server/agent-run-context.js +18 -0
- package/dist/server/agent-run-context.js.map +1 -1
- package/dist/server/capture-error.d.ts +8 -0
- package/dist/server/capture-error.d.ts.map +1 -1
- package/dist/server/capture-error.js.map +1 -1
- package/dist/server/core-routes-plugin.d.ts +0 -23
- package/dist/server/core-routes-plugin.d.ts.map +1 -1
- package/dist/server/core-routes-plugin.js +71 -7
- package/dist/server/core-routes-plugin.js.map +1 -1
- package/dist/server/deploy-environment.d.ts +17 -0
- package/dist/server/deploy-environment.d.ts.map +1 -0
- package/dist/server/deploy-environment.js +47 -0
- package/dist/server/deploy-environment.js.map +1 -0
- package/dist/server/error-noise-filter.d.ts +80 -0
- package/dist/server/error-noise-filter.d.ts.map +1 -0
- package/dist/server/error-noise-filter.js +173 -0
- package/dist/server/error-noise-filter.js.map +1 -0
- package/dist/server/posthog-config.d.ts +28 -0
- package/dist/server/posthog-config.d.ts.map +1 -0
- package/dist/server/posthog-config.js +53 -0
- package/dist/server/posthog-config.js.map +1 -0
- package/dist/server/request-context.d.ts +6 -0
- package/dist/server/request-context.d.ts.map +1 -1
- package/dist/server/request-context.js.map +1 -1
- package/dist/server/sentry-config.d.ts +1 -0
- package/dist/server/sentry-config.d.ts.map +1 -1
- package/dist/server/sentry-config.js +3 -1
- package/dist/server/sentry-config.js.map +1 -1
- package/dist/server/sentry-plugin.d.ts.map +1 -1
- package/dist/server/sentry-plugin.js +12 -27
- package/dist/server/sentry-plugin.js.map +1 -1
- package/dist/server/sentry.d.ts.map +1 -1
- package/dist/server/sentry.js +8 -124
- package/dist/server/sentry.js.map +1 -1
- package/dist/server/ssr-handler.d.ts.map +1 -1
- package/dist/server/ssr-handler.js +6 -1
- package/dist/server/ssr-handler.js.map +1 -1
- package/dist/shared/mcp-embed-headers.d.ts +1 -1
- package/dist/shared/mcp-embed-headers.d.ts.map +1 -1
- package/dist/shared/mcp-embed-headers.js +1 -1
- package/dist/shared/mcp-embed-headers.js.map +1 -1
- package/dist/templates/workspace-core/.agents/skills/observability/SKILL.md +36 -10
- package/dist/templates/workspace-core/.agents/skills/tracking/SKILL.md +55 -1
- package/dist/tracking/error-capture.d.ts +7 -0
- package/dist/tracking/error-capture.d.ts.map +1 -1
- package/dist/tracking/error-capture.js +10 -73
- package/dist/tracking/error-capture.js.map +1 -1
- package/dist/tracking/index.d.ts +1 -0
- package/dist/tracking/index.d.ts.map +1 -1
- package/dist/tracking/index.js +1 -0
- package/dist/tracking/index.js.map +1 -1
- package/dist/tracking/posthog-exception.d.ts +103 -0
- package/dist/tracking/posthog-exception.d.ts.map +1 -0
- package/dist/tracking/posthog-exception.js +181 -0
- package/dist/tracking/posthog-exception.js.map +1 -0
- package/dist/tracking/providers.d.ts +14 -0
- package/dist/tracking/providers.d.ts.map +1 -1
- package/dist/tracking/providers.js +63 -11
- package/dist/tracking/providers.js.map +1 -1
- package/dist/tracking/redaction.d.ts +25 -0
- package/dist/tracking/redaction.d.ts.map +1 -0
- package/dist/tracking/redaction.js +85 -0
- package/dist/tracking/redaction.js.map +1 -0
- package/docs/content/observability.mdx +41 -1
- package/docs/content/tracking.mdx +25 -0
- package/package.json +1 -1
- package/src/agent/run-manager.ts +7 -0
- package/src/client/agent-chat-adapter.ts +10 -0
- package/src/client/analytics.ts +105 -1
- package/src/client/use-action.ts +6 -0
- package/src/deploy/build.ts +38 -1
- package/src/observability/posthog-ai.ts +294 -0
- package/src/observability/routes.ts +29 -6
- package/src/observability/traces.ts +287 -12
- package/src/observability/types.ts +11 -0
- package/src/secrets/register-framework-secrets.ts +16 -0
- package/src/server/agent-run-context.ts +21 -0
- package/src/server/capture-error.ts +8 -0
- package/src/server/core-routes-plugin.ts +75 -4
- package/src/server/deploy-environment.ts +57 -0
- package/src/server/error-noise-filter.ts +268 -0
- package/src/server/posthog-config.ts +75 -0
- package/src/server/request-context.ts +6 -0
- package/src/server/sentry-config.ts +4 -8
- package/src/server/sentry-plugin.ts +13 -29
- package/src/server/sentry.ts +13 -156
- package/src/server/ssr-handler.ts +6 -1
- package/src/shared/mcp-embed-headers.ts +1 -1
- package/src/templates/workspace-core/.agents/skills/observability/SKILL.md +36 -10
- package/src/templates/workspace-core/.agents/skills/tracking/SKILL.md +55 -1
- package/src/tracking/error-capture.ts +44 -109
- package/src/tracking/index.ts +11 -0
- package/src/tracking/posthog-exception.ts +305 -0
- package/src/tracking/providers.ts +91 -14
- package/src/tracking/redaction.ts +105 -0
|
@@ -0,0 +1,294 @@
|
|
|
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
|
+
|
|
24
|
+
import { sendPostHogEvent } from "../tracking/providers.js";
|
|
25
|
+
import { boundedText } from "../tracking/redaction.js";
|
|
26
|
+
|
|
27
|
+
/** Hard ceiling on serialized content per `$ai_*` field. */
|
|
28
|
+
export const MAX_AI_CONTENT_BYTES = 128 * 1024;
|
|
29
|
+
/** Hard ceiling on emitted `$ai_span` events per run. */
|
|
30
|
+
export const MAX_AI_SPANS_PER_RUN = 100;
|
|
31
|
+
|
|
32
|
+
export interface AiErrorDetail {
|
|
33
|
+
message: string;
|
|
34
|
+
terminal_code?: string;
|
|
35
|
+
terminal_state?: string;
|
|
36
|
+
retryable?: boolean;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function trackAiEvent(
|
|
40
|
+
name: string,
|
|
41
|
+
properties: Record<string, unknown>,
|
|
42
|
+
userId: string | null,
|
|
43
|
+
): void {
|
|
44
|
+
for (const key of Object.keys(properties)) {
|
|
45
|
+
if (properties[key] === undefined) delete properties[key];
|
|
46
|
+
}
|
|
47
|
+
try {
|
|
48
|
+
void import("../tracking/registry.js")
|
|
49
|
+
.then(({ track }) => {
|
|
50
|
+
track(name, properties, { userId: userId ?? undefined });
|
|
51
|
+
})
|
|
52
|
+
.catch(() => {});
|
|
53
|
+
// coercion-ok: a throw here would break the run it is observing
|
|
54
|
+
} catch {
|
|
55
|
+
// Tracking must never affect the agent run or trace persistence.
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Serialize a content value under a byte ceiling.
|
|
61
|
+
*
|
|
62
|
+
* Returns `{ truncated: true }` with a placeholder rather than a silently
|
|
63
|
+
* shortened payload — a trace that shows half a conversation as if it were the
|
|
64
|
+
* whole one is worse than one that says it was cut.
|
|
65
|
+
*/
|
|
66
|
+
export function boundAiContent(value: unknown): {
|
|
67
|
+
value: unknown;
|
|
68
|
+
truncated: boolean;
|
|
69
|
+
} {
|
|
70
|
+
let serialized: string;
|
|
71
|
+
try {
|
|
72
|
+
serialized = JSON.stringify(value);
|
|
73
|
+
} catch {
|
|
74
|
+
return { value: "[unserializable]", truncated: true };
|
|
75
|
+
}
|
|
76
|
+
if (serialized === undefined) return { value: undefined, truncated: false };
|
|
77
|
+
|
|
78
|
+
const bytes =
|
|
79
|
+
typeof Buffer !== "undefined"
|
|
80
|
+
? Buffer.byteLength(serialized, "utf8")
|
|
81
|
+
: new TextEncoder().encode(serialized).length;
|
|
82
|
+
if (bytes <= MAX_AI_CONTENT_BYTES) return { value, truncated: false };
|
|
83
|
+
|
|
84
|
+
return {
|
|
85
|
+
value: `[truncated: ${bytes} bytes exceeded the ${MAX_AI_CONTENT_BYTES}-byte trace content limit]`,
|
|
86
|
+
truncated: true,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export interface AiTraceEventInput {
|
|
91
|
+
runId: string;
|
|
92
|
+
threadId: string | null;
|
|
93
|
+
userId: string | null;
|
|
94
|
+
/** Human-readable name for the run, e.g. the agent or thread name. */
|
|
95
|
+
spanName: string;
|
|
96
|
+
model: string;
|
|
97
|
+
provider: string;
|
|
98
|
+
latencySeconds: number;
|
|
99
|
+
isError: boolean;
|
|
100
|
+
error?: AiErrorDetail;
|
|
101
|
+
inputTokens?: number;
|
|
102
|
+
outputTokens?: number;
|
|
103
|
+
costUsd?: number;
|
|
104
|
+
createdAt: number;
|
|
105
|
+
/** Browser session id, when the run originated from a page. Links the trace
|
|
106
|
+
* to PostHog session replay. */
|
|
107
|
+
browserSessionId?: string;
|
|
108
|
+
/** Omitted unless `capturePrompts` is on. */
|
|
109
|
+
inputState?: unknown;
|
|
110
|
+
outputState?: unknown;
|
|
111
|
+
extraProperties?: Record<string, unknown>;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export function emitAiTraceEvent(input: AiTraceEventInput): void {
|
|
115
|
+
const inputContent =
|
|
116
|
+
input.inputState === undefined
|
|
117
|
+
? undefined
|
|
118
|
+
: boundAiContent(input.inputState);
|
|
119
|
+
const outputContent =
|
|
120
|
+
input.outputState === undefined
|
|
121
|
+
? undefined
|
|
122
|
+
: boundAiContent(input.outputState);
|
|
123
|
+
|
|
124
|
+
trackAiEvent(
|
|
125
|
+
"$ai_trace",
|
|
126
|
+
{
|
|
127
|
+
...input.extraProperties,
|
|
128
|
+
$ai_trace_id: input.runId,
|
|
129
|
+
$ai_session_id: input.threadId ?? undefined,
|
|
130
|
+
$ai_span_name: input.spanName,
|
|
131
|
+
$ai_model: input.model,
|
|
132
|
+
$ai_provider: input.provider,
|
|
133
|
+
$ai_latency: input.latencySeconds,
|
|
134
|
+
$ai_is_error: input.isError,
|
|
135
|
+
$ai_error: input.error,
|
|
136
|
+
$ai_input_tokens: input.inputTokens,
|
|
137
|
+
$ai_output_tokens: input.outputTokens,
|
|
138
|
+
$ai_total_cost_usd: input.costUsd,
|
|
139
|
+
$ai_input_state: inputContent?.value,
|
|
140
|
+
$ai_output_state: outputContent?.value,
|
|
141
|
+
$ai_input_truncated: inputContent?.truncated || undefined,
|
|
142
|
+
$ai_output_truncated: outputContent?.truncated || undefined,
|
|
143
|
+
$session_id: input.browserSessionId,
|
|
144
|
+
created_at: new Date(input.createdAt).toISOString(),
|
|
145
|
+
},
|
|
146
|
+
input.userId,
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export interface AiSpanEventInput {
|
|
151
|
+
runId: string;
|
|
152
|
+
threadId: string | null;
|
|
153
|
+
userId: string | null;
|
|
154
|
+
spanId: string;
|
|
155
|
+
/** Defaults to the run's trace id, which is the tree root. */
|
|
156
|
+
parentId?: string;
|
|
157
|
+
spanName: string;
|
|
158
|
+
latencySeconds: number;
|
|
159
|
+
isError: boolean;
|
|
160
|
+
error?: AiErrorDetail;
|
|
161
|
+
createdAt: number;
|
|
162
|
+
browserSessionId?: string;
|
|
163
|
+
/** Omitted unless `captureToolArgs` / `captureToolResults` are on. */
|
|
164
|
+
inputState?: unknown;
|
|
165
|
+
outputState?: unknown;
|
|
166
|
+
extraProperties?: Record<string, unknown>;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
export function emitAiSpanEvent(input: AiSpanEventInput): void {
|
|
170
|
+
const inputContent =
|
|
171
|
+
input.inputState === undefined
|
|
172
|
+
? undefined
|
|
173
|
+
: boundAiContent(input.inputState);
|
|
174
|
+
const outputContent =
|
|
175
|
+
input.outputState === undefined
|
|
176
|
+
? undefined
|
|
177
|
+
: boundAiContent(input.outputState);
|
|
178
|
+
|
|
179
|
+
trackAiEvent(
|
|
180
|
+
"$ai_span",
|
|
181
|
+
{
|
|
182
|
+
...input.extraProperties,
|
|
183
|
+
$ai_trace_id: input.runId,
|
|
184
|
+
$ai_session_id: input.threadId ?? undefined,
|
|
185
|
+
$ai_span_id: input.spanId,
|
|
186
|
+
$ai_parent_id: input.parentId ?? input.runId,
|
|
187
|
+
$ai_span_name: input.spanName,
|
|
188
|
+
$ai_latency: input.latencySeconds,
|
|
189
|
+
$ai_is_error: input.isError,
|
|
190
|
+
$ai_error: input.error,
|
|
191
|
+
$ai_input_state: inputContent?.value,
|
|
192
|
+
$ai_output_state: outputContent?.value,
|
|
193
|
+
$ai_input_truncated: inputContent?.truncated || undefined,
|
|
194
|
+
$ai_output_truncated: outputContent?.truncated || undefined,
|
|
195
|
+
$session_id: input.browserSessionId,
|
|
196
|
+
created_at: new Date(input.createdAt).toISOString(),
|
|
197
|
+
},
|
|
198
|
+
input.userId,
|
|
199
|
+
);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
export interface AiFeedbackSurveyInput {
|
|
203
|
+
runId: string | null;
|
|
204
|
+
threadId: string | null;
|
|
205
|
+
userId: string | null;
|
|
206
|
+
feedbackType: "thumbs_up" | "thumbs_down" | "category" | "text";
|
|
207
|
+
/** The submitted value: sentiment label, chosen category, or free text. */
|
|
208
|
+
value: string;
|
|
209
|
+
submissionId: string;
|
|
210
|
+
model?: string;
|
|
211
|
+
browserSessionId?: string;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Emit PostHog's documented manual feedback event for an LLM trace.
|
|
216
|
+
*
|
|
217
|
+
* PostHog surfaces feedback in LLM analytics only through `survey sent` keyed
|
|
218
|
+
* to a real survey id — `$ai_feedback` is not a PostHog event and renders
|
|
219
|
+
* nowhere. Returns `false` and emits nothing when no survey id is configured:
|
|
220
|
+
* inventing one would produce events attached to a survey that does not exist.
|
|
221
|
+
*
|
|
222
|
+
* Sent to PostHog ONLY, not through `track()`. The survey response carries the
|
|
223
|
+
* user's free-text feedback verbatim, and configuring a PostHog survey id must
|
|
224
|
+
* not silently start shipping that text to Mixpanel, Amplitude, webhooks, or
|
|
225
|
+
* Agent Native Analytics. Those backends get the content-free `$ai_feedback`
|
|
226
|
+
* event instead.
|
|
227
|
+
*
|
|
228
|
+
* @see https://posthog.com/docs/ai-observability/user-feedback/manual-event-capture
|
|
229
|
+
*/
|
|
230
|
+
export function emitAiFeedbackSurveyEvent(
|
|
231
|
+
input: AiFeedbackSurveyInput,
|
|
232
|
+
): boolean {
|
|
233
|
+
const surveyId = process.env.POSTHOG_AI_FEEDBACK_SURVEY_ID?.trim();
|
|
234
|
+
if (!surveyId) return false;
|
|
235
|
+
|
|
236
|
+
const questionId = process.env.POSTHOG_AI_FEEDBACK_SURVEY_QUESTION_ID?.trim();
|
|
237
|
+
// PostHog accepts `$survey_response` for a single-question survey and
|
|
238
|
+
// `$survey_response_<questionId>` when the survey has named questions.
|
|
239
|
+
const responseKey = questionId
|
|
240
|
+
? `$survey_response_${questionId}`
|
|
241
|
+
: "$survey_response";
|
|
242
|
+
|
|
243
|
+
const properties: Record<string, unknown> = {
|
|
244
|
+
$survey_id: surveyId,
|
|
245
|
+
[responseKey]: input.value,
|
|
246
|
+
$survey_submission_id: input.submissionId,
|
|
247
|
+
$survey_completed: true,
|
|
248
|
+
$ai_trace_id: input.runId ?? undefined,
|
|
249
|
+
$ai_session_id: input.threadId ?? undefined,
|
|
250
|
+
$ai_model: input.model,
|
|
251
|
+
$session_id: input.browserSessionId,
|
|
252
|
+
feedback_type: input.feedbackType,
|
|
253
|
+
source: "agent_observability",
|
|
254
|
+
};
|
|
255
|
+
for (const key of Object.keys(properties)) {
|
|
256
|
+
if (properties[key] === undefined) delete properties[key];
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
return sendPostHogEvent(
|
|
260
|
+
"survey sent",
|
|
261
|
+
properties,
|
|
262
|
+
input.userId ?? "anonymous",
|
|
263
|
+
);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Build a structured `$ai_error` from the run's failure information.
|
|
268
|
+
*
|
|
269
|
+
* Returns `undefined` when the run did not fail, so `$ai_error` is absent
|
|
270
|
+
* rather than an empty object on healthy traces.
|
|
271
|
+
*/
|
|
272
|
+
export function toAiErrorDetail(
|
|
273
|
+
errorMessage: string | null | undefined,
|
|
274
|
+
terminalOutcome?: {
|
|
275
|
+
state?: string;
|
|
276
|
+
code?: string;
|
|
277
|
+
retryable?: boolean;
|
|
278
|
+
},
|
|
279
|
+
): AiErrorDetail | undefined {
|
|
280
|
+
if (!errorMessage && !terminalOutcome?.code) return undefined;
|
|
281
|
+
return {
|
|
282
|
+
message: boundedText(
|
|
283
|
+
errorMessage ?? terminalOutcome?.code ?? "error",
|
|
284
|
+
1000,
|
|
285
|
+
),
|
|
286
|
+
...(terminalOutcome?.state
|
|
287
|
+
? { terminal_state: terminalOutcome.state }
|
|
288
|
+
: {}),
|
|
289
|
+
...(terminalOutcome?.code ? { terminal_code: terminalOutcome.code } : {}),
|
|
290
|
+
...(terminalOutcome?.retryable !== undefined
|
|
291
|
+
? { retryable: terminalOutcome.retryable }
|
|
292
|
+
: {}),
|
|
293
|
+
};
|
|
294
|
+
}
|
|
@@ -30,7 +30,9 @@ import {
|
|
|
30
30
|
|
|
31
31
|
import { getSession } from "../server/auth.js";
|
|
32
32
|
import { readBody } from "../server/h3-helpers.js";
|
|
33
|
+
import { getRequestContext } from "../server/request-context.js";
|
|
33
34
|
import { track } from "../tracking/registry.js";
|
|
35
|
+
import { emitAiFeedbackSurveyEvent } from "./posthog-ai.js";
|
|
34
36
|
import {
|
|
35
37
|
getObservabilityOverview,
|
|
36
38
|
getTraceSummaries,
|
|
@@ -207,11 +209,11 @@ export function createObservabilityHandler() {
|
|
|
207
209
|
userId: owner,
|
|
208
210
|
createdAt: Date.now(),
|
|
209
211
|
});
|
|
210
|
-
|
|
211
|
-
// Category follow-ups intentionally do not emit: a thumbs-down followed
|
|
212
|
-
// by a category would otherwise double-count the same negative signal.
|
|
213
|
-
if (feedbackType === "thumbs_up" || feedbackType === "thumbs_down") {
|
|
212
|
+
{
|
|
214
213
|
const runId = body.runId ? String(body.runId) : null;
|
|
214
|
+
const threadId = body.threadId ? String(body.threadId) : null;
|
|
215
|
+
const isThumb =
|
|
216
|
+
feedbackType === "thumbs_up" || feedbackType === "thumbs_down";
|
|
215
217
|
let model: string | undefined;
|
|
216
218
|
if (runId) {
|
|
217
219
|
try {
|
|
@@ -223,13 +225,21 @@ export function createObservabilityHandler() {
|
|
|
223
225
|
}
|
|
224
226
|
}
|
|
225
227
|
|
|
226
|
-
|
|
228
|
+
// Every submission is reported, including `category` and `text`, which
|
|
229
|
+
// previously emitted nothing at all. Only thumbs carry `sentiment` —
|
|
230
|
+
// a category follow-up to a thumbs-down is extra detail about the same
|
|
231
|
+
// vote, so counting it as a second negative would inflate the metric.
|
|
227
232
|
track(
|
|
228
233
|
"$ai_feedback",
|
|
229
234
|
{
|
|
230
235
|
...trackingIdentityProperties(),
|
|
231
236
|
source: "agent_observability",
|
|
232
|
-
|
|
237
|
+
...(isThumb
|
|
238
|
+
? {
|
|
239
|
+
sentiment:
|
|
240
|
+
feedbackType === "thumbs_up" ? "positive" : "negative",
|
|
241
|
+
}
|
|
242
|
+
: {}),
|
|
233
243
|
feedback_type: feedbackType,
|
|
234
244
|
run_id: runId,
|
|
235
245
|
thread_id: threadId,
|
|
@@ -240,6 +250,19 @@ export function createObservabilityHandler() {
|
|
|
240
250
|
},
|
|
241
251
|
{ userId: owner },
|
|
242
252
|
);
|
|
253
|
+
|
|
254
|
+
// PostHog shows feedback in LLM analytics only via `survey sent`.
|
|
255
|
+
// No-ops unless a survey id is configured.
|
|
256
|
+
emitAiFeedbackSurveyEvent({
|
|
257
|
+
runId,
|
|
258
|
+
threadId,
|
|
259
|
+
userId: owner,
|
|
260
|
+
feedbackType,
|
|
261
|
+
value: isThumb ? feedbackType : value,
|
|
262
|
+
submissionId: id,
|
|
263
|
+
model,
|
|
264
|
+
browserSessionId: getRequestContext()?.browserSessionId,
|
|
265
|
+
});
|
|
243
266
|
}
|
|
244
267
|
// Fire-and-forget: recompute satisfaction score for the thread.
|
|
245
268
|
if (body.threadId) {
|