@amsterdamdatalabs/traces 0.1.0 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -13,7 +13,7 @@
13
13
  - **Span attribute constants** — `enact.*` and `llm.*` extension attributes on top of the OpenInference semantic conventions
14
14
  - **Emit** — a zero-dependency `emitEvent()` that writes NDJSON to the watchdog dir and publishes to Apache Iggy in parallel
15
15
 
16
- Used by `enact-gateway`, `enact-factory`, `enact-memory`, `enact-agent`, and `enact-watchdog`.
16
+ Used by `enact-gateway`, `enact-factory`, `enact-wiki`, `enact-agent`, and `enact-watchdog`.
17
17
 
18
18
  ## Installation
19
19
 
@@ -104,7 +104,7 @@ export declare const ENACT_ITEMS_PROCESSED: "enact.items_processed";
104
104
  export declare const ENACT_QUERY_HASH: "enact.query_hash";
105
105
  export declare const ENACT_RESULTS_COUNT: "enact.results_count";
106
106
  export declare const ENACT_SLUG: "enact.slug";
107
- export declare const ENACT_MEMORY_TYPE: "enact.memory_type";
107
+ export declare const ENACT_WIKI_TYPE: "enact.wiki_type";
108
108
  export declare const ENACT_IMPORTANCE: "enact.importance";
109
109
  export declare const ENACT_SOURCE: "enact.source";
110
110
  export declare const ENACT_TOKENS_USED: "enact.tokens_used";
@@ -122,7 +122,7 @@ export const ENACT_ITEMS_PROCESSED = 'enact.items_processed';
122
122
  export const ENACT_QUERY_HASH = 'enact.query_hash';
123
123
  export const ENACT_RESULTS_COUNT = 'enact.results_count';
124
124
  export const ENACT_SLUG = 'enact.slug';
125
- export const ENACT_MEMORY_TYPE = 'enact.memory_type';
125
+ export const ENACT_WIKI_TYPE = 'enact.wiki_type';
126
126
  export const ENACT_IMPORTANCE = 'enact.importance';
127
127
  // enact.* extension attributes — agent
128
128
  export const ENACT_SOURCE = 'enact.source';
@@ -3,9 +3,13 @@ export type { EnactRawV1Event };
3
3
  export interface EmitOptions {
4
4
  /** NDJSON filename within the watchdog dir, e.g. 'factory-events.ndjson' */
5
5
  ndjsonFile: string;
6
- /** Override ENACT_WATCHDOG_DIR env var */
6
+ /** Explicit override for watchdog dir */
7
7
  watchdogDir?: string;
8
- /** Override IGGY_HTTP_URL env var */
8
+ /** Explicit override for Iggy HTTP base URL */
9
9
  iggyUrl?: string;
10
+ /** Explicit override for the workspace-scoped Iggy stream */
11
+ iggyStream?: string;
12
+ /** Explicit override for the logical Iggy topic */
13
+ iggyTopic?: string;
10
14
  }
11
15
  export declare function emitEvent(event: EnactRawV1Event, opts: EmitOptions): Promise<void>;
package/dist/emit/emit.js CHANGED
@@ -1,29 +1,27 @@
1
1
  import { appendFileSync, mkdirSync } from 'node:fs';
2
2
  import { join } from 'node:path';
3
- const DEFAULT_IGGY_URL = 'http://127.0.0.1:43214';
3
+ import { rootIggyHttpUrl, rootIggyPassword, rootIggyTopic, rootScopedIggyStream, rootIggyUsername, rootWatchdogDir, } from './root-config.js';
4
4
  export async function emitEvent(event, opts) {
5
- const watchdogDir = opts.watchdogDir ?? process.env.ENACT_WATCHDOG_DIR ?? '.enact/watchdog';
5
+ const watchdogDir = opts.watchdogDir ??
6
+ rootWatchdogDir();
6
7
  mkdirSync(watchdogDir, { recursive: true });
7
8
  const ndjsonPath = join(watchdogDir, opts.ndjsonFile);
8
- const rawIggyUrl = opts.iggyUrl ?? process.env.IGGY_HTTP_URL;
9
- const iggyUrl = !rawIggyUrl || rawIggyUrl === 'undefined' || rawIggyUrl === 'null'
10
- ? DEFAULT_IGGY_URL
11
- : rawIggyUrl;
9
+ const iggyUrl = opts.iggyUrl ?? rootIggyHttpUrl();
10
+ const iggyStream = opts.iggyStream ?? rootScopedIggyStream();
11
+ const iggyTopic = opts.iggyTopic ?? rootIggyTopic();
12
12
  const results = await Promise.allSettled([
13
13
  Promise.resolve().then(() => {
14
14
  appendFileSync(ndjsonPath, `${JSON.stringify(event)}\n`, 'utf8');
15
15
  }),
16
- publishToIggy(iggyUrl, event),
16
+ publishToIggy(iggyUrl, iggyStream, iggyTopic, event),
17
17
  ]);
18
18
  for (const result of results) {
19
19
  if (result.status === 'rejected') {
20
- if (process.env.ENACT_OBSERVABILITY_DEBUG) {
21
- console.error(`[enact-traces] emit error: ${String(result.reason)}`);
22
- }
20
+ console.error(`[enact-traces] emit error: ${String(result.reason)}`);
23
21
  }
24
22
  }
25
23
  }
26
- async function publishToIggy(baseUrl, event) {
24
+ async function publishToIggy(baseUrl, stream, topic, event) {
27
25
  const authHeader = await resolveIggyAuthHeader(baseUrl);
28
26
  const payload = {
29
27
  partitioning: { kind: 'balanced', value: '' },
@@ -35,7 +33,7 @@ async function publishToIggy(baseUrl, event) {
35
33
  ],
36
34
  };
37
35
  try {
38
- const response = await fetch(`${baseUrl}/streams/enact-telemetry/topics/worker_telemetry/messages`, {
36
+ const response = await fetch(`${baseUrl}/streams/${stream}/topics/${topic}/messages`, {
39
37
  method: 'POST',
40
38
  headers: {
41
39
  'Content-Type': 'application/json',
@@ -61,8 +59,8 @@ async function resolveIggyAuthHeader(baseUrl) {
61
59
  if (rawToken?.trim()) {
62
60
  return `Bearer ${rawToken.trim()}`;
63
61
  }
64
- const username = process.env.IGGY_HTTP_USERNAME ?? process.env.IGGY_ROOT_USERNAME ?? 'iggy';
65
- const password = process.env.IGGY_HTTP_PASSWORD ?? process.env.IGGY_ROOT_PASSWORD ?? 'iggy';
62
+ const username = rootIggyUsername();
63
+ const password = rootIggyPassword();
66
64
  const response = await fetch(`${baseUrl}/users/login`, {
67
65
  method: 'POST',
68
66
  headers: { 'Content-Type': 'application/json' },
@@ -0,0 +1,60 @@
1
+ interface RootPathsCfg {
2
+ use_global?: boolean;
3
+ project?: string;
4
+ global?: string;
5
+ }
6
+ interface RootWatchdogCfg {
7
+ iggy_http_url?: string;
8
+ iggy_username?: string;
9
+ iggy_password?: string;
10
+ topic?: string;
11
+ }
12
+ interface RootIggyCfg {
13
+ host?: string;
14
+ port?: number;
15
+ stream?: string;
16
+ topic?: string;
17
+ }
18
+ interface RootConfig {
19
+ paths?: RootPathsCfg;
20
+ agent?: {
21
+ watchdog_dir?: string;
22
+ };
23
+ watchdog?: RootWatchdogCfg;
24
+ iggy?: RootIggyCfg;
25
+ }
26
+ export interface ResolvedConfigContext {
27
+ merged: RootConfig;
28
+ globalConfig: RootConfig | null;
29
+ workspaceConfig: RootConfig | null;
30
+ globalConfigPath: string | null;
31
+ globalConfigDir: string;
32
+ workspaceConfigPath: string | null;
33
+ workspaceRoot: string | null;
34
+ }
35
+ export declare function resetResolvedConfigCacheForTests(): void;
36
+ export declare function readRootConfig(): RootConfig;
37
+ export declare function resolveConfigContext(): ResolvedConfigContext;
38
+ /**
39
+ * Resolve a per-key string value, honoring the provenance rule: relative paths
40
+ * resolve against the directory of the config.toml that declared the value.
41
+ * Workspace declarations win over global; global declarations resolve against
42
+ * `~/.enact`.
43
+ */
44
+ export declare function resolveDeclaredPath(workspaceValue: string | undefined, globalValue: string | undefined, context?: ResolvedConfigContext): string | null;
45
+ export declare function expandPathFromBase(value: string, sourceDir: string): string;
46
+ /**
47
+ * Resolve the runtime base dir (the `[paths]`-selected root). Each individual
48
+ * field in `[paths]` honors per-key provenance, so a workspace-set `use_global`
49
+ * combined with a global-set `global` is supported correctly.
50
+ */
51
+ export declare function resolveRuntimeBaseDir(context?: ResolvedConfigContext): string;
52
+ export declare function rootWatchdogDir(): string;
53
+ export declare function rootIggyHttpUrl(): string;
54
+ export declare function rootIggyUsername(): string;
55
+ export declare function rootIggyPassword(): string;
56
+ export declare function rootIggyStream(): string;
57
+ export declare function rootIggyTopic(): string;
58
+ export declare function deriveWorkspaceId(root?: string): string;
59
+ export declare function rootScopedIggyStream(): string;
60
+ export {};
@@ -0,0 +1,242 @@
1
+ import { existsSync, readFileSync } from 'node:fs';
2
+ import { dirname, isAbsolute, join, resolve, basename } from 'node:path';
3
+ import { homedir } from 'node:os';
4
+ import { createHash } from 'node:crypto';
5
+ const DEFAULT_PROJECT = '.enact';
6
+ const DEFAULT_GLOBAL = '~/.enact';
7
+ let cachedContext = null;
8
+ export function resetResolvedConfigCacheForTests() {
9
+ cachedContext = null;
10
+ }
11
+ export function readRootConfig() {
12
+ return resolveConfigContext().merged;
13
+ }
14
+ export function resolveConfigContext() {
15
+ if (cachedContext)
16
+ return cachedContext;
17
+ const filename = configFileName();
18
+ const globalConfigDir = join(homedir(), '.enact');
19
+ const globalConfigPath = join(globalConfigDir, filename);
20
+ const workspaceConfigPath = findWorkspaceConfigToml(filename);
21
+ const globalConfig = readConfigIfExists(globalConfigPath);
22
+ const workspaceConfig = workspaceConfigPath ? readConfigIfExists(workspaceConfigPath) : null;
23
+ if (!globalConfig && !workspaceConfig) {
24
+ throw new Error('Missing required root config.toml / config.prd.toml');
25
+ }
26
+ const merged = mergeRootConfig(globalConfig ?? {}, workspaceConfig ?? {});
27
+ cachedContext = {
28
+ merged,
29
+ globalConfig,
30
+ workspaceConfig,
31
+ globalConfigPath: globalConfig ? globalConfigPath : null,
32
+ globalConfigDir,
33
+ workspaceConfigPath,
34
+ workspaceRoot: workspaceConfigPath ? dirname(workspaceConfigPath) : null,
35
+ };
36
+ return cachedContext;
37
+ }
38
+ /**
39
+ * Resolve a per-key string value, honoring the provenance rule: relative paths
40
+ * resolve against the directory of the config.toml that declared the value.
41
+ * Workspace declarations win over global; global declarations resolve against
42
+ * `~/.enact`.
43
+ */
44
+ export function resolveDeclaredPath(workspaceValue, globalValue, context = resolveConfigContext()) {
45
+ const declared = pickDeclared(workspaceValue, globalValue, context);
46
+ if (!declared)
47
+ return null;
48
+ return expandPathFromBase(declared.value, declared.sourceDir);
49
+ }
50
+ export function expandPathFromBase(value, sourceDir) {
51
+ const trimmed = value.trim();
52
+ if (!trimmed)
53
+ return trimmed;
54
+ if (trimmed === '~')
55
+ return homedir();
56
+ if (trimmed.startsWith('~/'))
57
+ return join(homedir(), trimmed.slice(2));
58
+ if (isAbsolute(trimmed))
59
+ return trimmed;
60
+ return resolve(sourceDir, trimmed);
61
+ }
62
+ /**
63
+ * Resolve the runtime base dir (the `[paths]`-selected root). Each individual
64
+ * field in `[paths]` honors per-key provenance, so a workspace-set `use_global`
65
+ * combined with a global-set `global` is supported correctly.
66
+ */
67
+ export function resolveRuntimeBaseDir(context = resolveConfigContext()) {
68
+ const useGlobal = context.workspaceConfig?.paths?.use_global ??
69
+ context.globalConfig?.paths?.use_global ??
70
+ false;
71
+ if (useGlobal) {
72
+ const resolved = resolveDeclaredPath(context.workspaceConfig?.paths?.global, context.globalConfig?.paths?.global, context);
73
+ return resolved ?? expandPathFromBase(DEFAULT_GLOBAL, context.globalConfigDir);
74
+ }
75
+ const resolved = resolveDeclaredPath(context.workspaceConfig?.paths?.project, context.globalConfig?.paths?.project, context);
76
+ if (resolved)
77
+ return resolved;
78
+ // Default: project relative to the *workspace* config dir; fall back to home if absent.
79
+ const base = context.workspaceRoot ?? context.globalConfigDir;
80
+ return expandPathFromBase(DEFAULT_PROJECT, base);
81
+ }
82
+ export function rootWatchdogDir() {
83
+ const context = resolveConfigContext();
84
+ const declared = resolveDeclaredPath(context.workspaceConfig?.agent?.watchdog_dir, context.globalConfig?.agent?.watchdog_dir, context);
85
+ if (declared)
86
+ return declared;
87
+ return join(resolveRuntimeBaseDir(context), 'watchdog');
88
+ }
89
+ export function rootIggyHttpUrl() {
90
+ const config = readRootConfig();
91
+ if (config?.watchdog?.iggy_http_url)
92
+ return config.watchdog.iggy_http_url;
93
+ if (!config?.iggy?.host || typeof config.iggy.port !== 'number') {
94
+ throw new Error('Missing required iggy.host/iggy.port in root config for enact-traces');
95
+ }
96
+ return `http://${config.iggy.host}:${config.iggy.port}`;
97
+ }
98
+ export function rootIggyUsername() {
99
+ const username = readRootConfig()?.watchdog?.iggy_username;
100
+ if (!username) {
101
+ throw new Error('Missing required watchdog.iggy_username in root config for enact-traces');
102
+ }
103
+ return username;
104
+ }
105
+ export function rootIggyPassword() {
106
+ const password = readRootConfig()?.watchdog?.iggy_password;
107
+ if (!password) {
108
+ throw new Error('Missing required watchdog.iggy_password in root config for enact-traces');
109
+ }
110
+ return password;
111
+ }
112
+ export function rootIggyStream() {
113
+ const config = readRootConfig();
114
+ const stream = config.iggy?.stream;
115
+ if (!stream) {
116
+ throw new Error('Missing required iggy.stream in root config for enact-traces');
117
+ }
118
+ return stream;
119
+ }
120
+ export function rootIggyTopic() {
121
+ const config = readRootConfig();
122
+ const topic = config.iggy?.topic;
123
+ if (!topic) {
124
+ throw new Error('Missing required iggy.topic in root config for enact-traces');
125
+ }
126
+ return topic;
127
+ }
128
+ export function deriveWorkspaceId(root = resolveConfigContext().workspaceRoot ?? process.cwd()) {
129
+ const resolvedRoot = resolve(root);
130
+ const name = basename(resolvedRoot) || 'workspace';
131
+ const hash6 = createHash('sha256').update(resolvedRoot).digest('hex').slice(0, 6);
132
+ return `${name}-${hash6}`;
133
+ }
134
+ export function rootScopedIggyStream() {
135
+ return `ws_${deriveWorkspaceId()}_${rootIggyStream()}`;
136
+ }
137
+ function pickDeclared(workspaceValue, globalValue, context) {
138
+ if (typeof workspaceValue === 'string' && workspaceValue.trim() && context.workspaceRoot) {
139
+ return { value: workspaceValue, sourceDir: context.workspaceRoot };
140
+ }
141
+ if (typeof globalValue === 'string' && globalValue.trim() && context.globalConfigPath) {
142
+ return { value: globalValue, sourceDir: context.globalConfigDir };
143
+ }
144
+ return null;
145
+ }
146
+ function configFileName() {
147
+ return process.env.NODE_ENV === 'production' ? 'config.prd.toml' : 'config.toml';
148
+ }
149
+ function findWorkspaceConfigToml(filename) {
150
+ const home = resolve(homedir());
151
+ let dir = process.cwd();
152
+ while (true) {
153
+ const candidate = join(dir, filename);
154
+ if (existsSync(candidate))
155
+ return candidate;
156
+ if (resolve(dir) === home)
157
+ return null;
158
+ const parent = dirname(dir);
159
+ if (parent === dir)
160
+ return null;
161
+ dir = parent;
162
+ }
163
+ }
164
+ function readConfigIfExists(path) {
165
+ if (!existsSync(path))
166
+ return null;
167
+ try {
168
+ return parseRootConfig(readFileSync(path, 'utf8'));
169
+ }
170
+ catch (error) {
171
+ throw new Error(`Invalid root config.toml / config.prd.toml for enact-traces at ${path}: ${error instanceof Error ? error.message : String(error)}`);
172
+ }
173
+ }
174
+ function mergeRootConfig(base, overlay) {
175
+ return {
176
+ ...base,
177
+ ...overlay,
178
+ paths: { ...(base.paths ?? {}), ...(overlay.paths ?? {}) },
179
+ agent: { ...(base.agent ?? {}), ...(overlay.agent ?? {}) },
180
+ watchdog: { ...(base.watchdog ?? {}), ...(overlay.watchdog ?? {}) },
181
+ iggy: { ...(base.iggy ?? {}), ...(overlay.iggy ?? {}) },
182
+ };
183
+ }
184
+ function parseRootConfig(raw) {
185
+ const config = {};
186
+ let section = '';
187
+ for (const originalLine of raw.split('\n')) {
188
+ const line = originalLine.replace(/#.*$/, '').trim();
189
+ if (!line)
190
+ continue;
191
+ const sectionMatch = line.match(/^\[([^\]]+)\]$/);
192
+ if (sectionMatch) {
193
+ section = sectionMatch[1] ?? '';
194
+ continue;
195
+ }
196
+ const kvMatch = line.match(/^([A-Za-z0-9_]+)\s*=\s*(.+)$/);
197
+ if (!kvMatch)
198
+ continue;
199
+ const key = kvMatch[1];
200
+ const value = parseValue(kvMatch[2]);
201
+ assignValue(config, section, key, value);
202
+ }
203
+ return config;
204
+ }
205
+ function parseValue(raw) {
206
+ const value = raw.trim();
207
+ if (value === 'true')
208
+ return true;
209
+ if (value === 'false')
210
+ return false;
211
+ if (/^\d+$/.test(value))
212
+ return Number(value);
213
+ if (value.startsWith('[') && value.endsWith(']')) {
214
+ return value
215
+ .slice(1, -1)
216
+ .split(',')
217
+ .map((part) => stripQuotes(part.trim()))
218
+ .filter(Boolean);
219
+ }
220
+ return stripQuotes(value);
221
+ }
222
+ function stripQuotes(value) {
223
+ return value.replace(/^"(.*)"$/, '$1').replace(/^'(.*)'$/, '$1');
224
+ }
225
+ function assignValue(config, section, key, value) {
226
+ if (section === 'paths') {
227
+ config.paths ??= {};
228
+ config.paths[key] = value;
229
+ }
230
+ else if (section === 'agent') {
231
+ config.agent ??= {};
232
+ config.agent[key] = value;
233
+ }
234
+ else if (section === 'watchdog') {
235
+ config.watchdog ??= {};
236
+ config.watchdog[key] = value;
237
+ }
238
+ else if (section === 'iggy') {
239
+ config.iggy ??= {};
240
+ config.iggy[key] = value;
241
+ }
242
+ }
package/dist/index.d.ts CHANGED
@@ -2,3 +2,4 @@ export * from './conventions/index.js';
2
2
  export * from './types/index.js';
3
3
  export * from './payloads/index.js';
4
4
  export * from './emit/emit.js';
5
+ export * from './emit/root-config.js';
package/dist/index.js CHANGED
@@ -2,3 +2,4 @@ export * from './conventions/index.js';
2
2
  export * from './types/index.js';
3
3
  export * from './payloads/index.js';
4
4
  export * from './emit/emit.js';
5
+ export * from './emit/root-config.js';
@@ -4,11 +4,38 @@ export interface AgentThreadStartedPayload {
4
4
  'session.id'?: string;
5
5
  [key: string]: unknown;
6
6
  }
7
- export interface AgentTurnPayload {
7
+ export interface AgentTurnStartPayload {
8
8
  'openinference.span.kind': 'AGENT';
9
- 'enact.source'?: string;
10
- turn_index?: number;
11
- [key: string]: unknown;
9
+ stage: 'turn.start';
10
+ turn_id: string;
11
+ model?: string;
12
+ cwd?: string;
13
+ 'agent.name'?: string;
14
+ 'input.value'?: string;
15
+ 'llm.invocation_parameters'?: string;
16
+ 'enact.collaboration_mode'?: string;
17
+ metadata?: string;
18
+ }
19
+ export interface AgentTurnEndPayload {
20
+ 'openinference.span.kind': 'AGENT';
21
+ stage: 'turn.end';
22
+ turn_id: string;
23
+ duration_ms: number;
24
+ status: string;
25
+ 'output.value'?: string;
26
+ 'llm.token_count.prompt'?: number;
27
+ 'llm.token_count.completion'?: number;
28
+ 'llm.token_count.total'?: number;
29
+ 'enact.success'?: boolean;
30
+ 'enact.duration_ms'?: number;
31
+ /** The user's original prompt text — enables full replay of the turn. */
32
+ 'input.value'?: string;
33
+ /** Canonical OpenInference ordered input messages (system prompt at index 0 if present, user prompt follows). */
34
+ 'llm.input_messages.0.message.role'?: string;
35
+ 'llm.input_messages.0.message.content'?: string;
36
+ 'llm.input_messages.1.message.role'?: string;
37
+ 'llm.input_messages.1.message.content'?: string;
38
+ metadata?: string;
12
39
  }
13
40
  export interface AgentTokenUsagePayload {
14
41
  'openinference.span.kind': 'CHAIN';
@@ -24,4 +51,124 @@ export interface AgentErrorPayload {
24
51
  'enact.message'?: string;
25
52
  error_type?: string;
26
53
  }
27
- export type AgentLifecyclePayload = AgentThreadStartedPayload | AgentTurnPayload | AgentTokenUsagePayload | AgentErrorPayload;
54
+ export interface AgentHookStartedPayload {
55
+ 'openinference.span.kind': 'TOOL';
56
+ stage: 'hook.started';
57
+ turn_id?: string;
58
+ hook_id: string;
59
+ event_name: string;
60
+ handler_type: string;
61
+ scope: string;
62
+ 'tool.name'?: string;
63
+ 'enact.hook_event'?: string;
64
+ 'enact.preset'?: string;
65
+ 'input.value'?: string;
66
+ metadata?: string;
67
+ }
68
+ export interface AgentHookCompletedPayload {
69
+ 'openinference.span.kind': 'TOOL';
70
+ stage: 'hook.completed';
71
+ turn_id?: string;
72
+ hook_id: string;
73
+ event_name: string;
74
+ status: string;
75
+ duration_ms?: number;
76
+ 'tool.name'?: string;
77
+ 'enact.hook_event'?: string;
78
+ 'enact.outcome'?: string;
79
+ 'enact.success'?: boolean;
80
+ 'enact.duration_ms'?: number;
81
+ 'output.value'?: string;
82
+ 'exception.message'?: string;
83
+ /** The hook's trigger input (e.g. user prompt text for UserPromptSubmit hooks). */
84
+ 'input.value'?: string;
85
+ metadata?: string;
86
+ }
87
+ export interface AgentToolDispatchedPayload {
88
+ 'openinference.span.kind': 'TOOL';
89
+ stage: 'tool.dispatched';
90
+ turn_id: string;
91
+ tool_call_id: string;
92
+ tool_name: string;
93
+ tool_namespace?: string | null;
94
+ /** OpenInference canonical tool identifier — equals tool_call_id. */
95
+ 'tool.id'?: string;
96
+ 'tool.parameters'?: string;
97
+ /** OpenInference tool description from registry spec. */
98
+ 'tool.description'?: string;
99
+ /** OpenInference tool JSON schema from registry spec. */
100
+ 'tool.json_schema'?: string;
101
+ 'input.value'?: string;
102
+ metadata?: string;
103
+ }
104
+ export interface AgentToolCompletedPayload {
105
+ 'openinference.span.kind': 'TOOL';
106
+ stage: 'tool.completed';
107
+ turn_id: string;
108
+ tool_call_id: string;
109
+ tool_name: string;
110
+ tool_namespace?: string | null;
111
+ /** OpenInference canonical tool identifier — equals tool_call_id. */
112
+ 'tool.id'?: string;
113
+ 'tool.output'?: string;
114
+ 'output.value'?: string;
115
+ duration_ms: number;
116
+ 'enact.duration_ms'?: number;
117
+ success: boolean;
118
+ 'enact.success'?: boolean;
119
+ 'exception.message'?: string;
120
+ /** The serialized tool parameters — enables full replay of the tool call. */
121
+ 'tool.parameters'?: string;
122
+ /** OpenInference tool description from registry spec. */
123
+ 'tool.description'?: string;
124
+ /** OpenInference tool JSON schema from registry spec. */
125
+ 'tool.json_schema'?: string;
126
+ /** Wire alias of tool.parameters projected by build_envelope. */
127
+ 'input.value'?: string;
128
+ metadata?: string;
129
+ }
130
+ export interface AgentSkillActivatedPayload {
131
+ 'openinference.span.kind': 'CHAIN';
132
+ stage: 'skill.activated';
133
+ skill_id: string;
134
+ skill_name: string;
135
+ activator: string;
136
+ scope: string;
137
+ 'tool.name'?: string;
138
+ 'enact.scope'?: string;
139
+ 'input.value'?: string;
140
+ metadata?: string;
141
+ }
142
+ export interface AgentSkillCompletedPayload {
143
+ 'openinference.span.kind': 'CHAIN';
144
+ stage: 'skill.completed';
145
+ skill_id: string;
146
+ skill_name: string;
147
+ scope: string;
148
+ duration_ms: number;
149
+ success: boolean;
150
+ 'tool.name'?: string;
151
+ 'enact.scope'?: string;
152
+ 'enact.duration_ms'?: number;
153
+ 'enact.success'?: boolean;
154
+ 'output.value'?: string;
155
+ 'exception.message'?: string;
156
+ 'exception.type'?: string;
157
+ metadata?: string;
158
+ }
159
+ export interface AgentPluginLoadedPayload {
160
+ 'openinference.span.kind': 'CHAIN';
161
+ stage: 'plugin.loaded';
162
+ plugin_id: string;
163
+ plugin_name: string;
164
+ source: string;
165
+ version?: string | null;
166
+ success: boolean;
167
+ 'tool.name'?: string;
168
+ 'enact.success'?: boolean;
169
+ 'exception.message'?: string;
170
+ 'exception.type'?: string;
171
+ 'exception.stacktrace'?: string;
172
+ metadata?: string;
173
+ }
174
+ export type AgentLifecyclePayload = AgentThreadStartedPayload | AgentTurnStartPayload | AgentTurnEndPayload | AgentTokenUsagePayload | AgentErrorPayload | AgentHookStartedPayload | AgentHookCompletedPayload | AgentToolDispatchedPayload | AgentToolCompletedPayload | AgentSkillActivatedPayload | AgentSkillCompletedPayload | AgentPluginLoadedPayload;
@@ -0,0 +1,83 @@
1
+ export interface EvolveTranscriptMessageRecordedPayload {
2
+ 'openinference.span.kind': 'AGENT';
3
+ role: string;
4
+ content: string;
5
+ timestamp?: string;
6
+ }
7
+ export interface EvolveTranscriptPromptRecordedPayload {
8
+ 'openinference.span.kind': 'CHAIN';
9
+ prompt_kind: string;
10
+ source?: string;
11
+ content: string;
12
+ timestamp?: string;
13
+ }
14
+ export interface EvolveTranscriptToolRecordedPayload {
15
+ 'openinference.span.kind': 'TOOL';
16
+ tool_id?: string;
17
+ 'tool.name': string;
18
+ 'tool.parameters'?: string;
19
+ 'tool.output'?: string;
20
+ 'enact.success'?: boolean;
21
+ status: string;
22
+ timestamp?: string;
23
+ }
24
+ export interface EvolveTranscriptHookRecordedPayload {
25
+ 'openinference.span.kind': 'CHAIN';
26
+ hook_name: string;
27
+ hook_event: string;
28
+ status: string;
29
+ command?: string;
30
+ content?: string;
31
+ timestamp?: string;
32
+ }
33
+ export interface EvolveTranscriptSkillRecordedPayload {
34
+ 'openinference.span.kind': 'CHAIN';
35
+ skill_name: string;
36
+ status: string;
37
+ description?: string;
38
+ content?: string;
39
+ timestamp?: string;
40
+ }
41
+ export interface EvolveTranscriptContextRecordedPayload {
42
+ 'openinference.span.kind': 'CHAIN';
43
+ context_kind: string;
44
+ label: string;
45
+ content: string;
46
+ timestamp?: string;
47
+ }
48
+ export interface EvolveEvidenceNormalizedPayload {
49
+ 'openinference.span.kind': 'CHAIN';
50
+ evidence_id: string;
51
+ surface: string;
52
+ files_touched: number;
53
+ failure_count: number;
54
+ prompt_count: number;
55
+ hook_count: number;
56
+ skill_count: number;
57
+ context_count: number;
58
+ tool_summary_count?: number;
59
+ hook_summary_count?: number;
60
+ trace_event_summary_count?: number;
61
+ }
62
+ export interface EvolveMetricComputedPayload {
63
+ 'openinference.span.kind': 'CHAIN';
64
+ read_edit_ratio?: number | null;
65
+ research_mutation_ratio?: number | null;
66
+ edit_without_read_count?: number;
67
+ repeated_edit_open_loops?: number;
68
+ tool_failure_retry_loops?: number;
69
+ watchdog_failure_count?: number;
70
+ watchdog_recovery_count?: number;
71
+ user_correction_markers?: number;
72
+ cost_latency_records?: number;
73
+ retrieval_miss_markers?: number;
74
+ task_closeout_status?: string;
75
+ }
76
+ export interface EvolveCandidateProposedPayload {
77
+ 'openinference.span.kind': 'CHAIN';
78
+ candidate_id: string;
79
+ target_family: string;
80
+ policy_route: string;
81
+ source_signals: string[];
82
+ }
83
+ export type EvolveEventPayload = EvolveTranscriptMessageRecordedPayload | EvolveTranscriptPromptRecordedPayload | EvolveTranscriptToolRecordedPayload | EvolveTranscriptHookRecordedPayload | EvolveTranscriptSkillRecordedPayload | EvolveTranscriptContextRecordedPayload | EvolveEvidenceNormalizedPayload | EvolveMetricComputedPayload | EvolveCandidateProposedPayload;
@@ -0,0 +1 @@
1
+ export {};
@@ -7,3 +7,4 @@ export * from './cache-hit.js';
7
7
  export * from './agent-lifecycle.js';
8
8
  export * from './proc-tap.js';
9
9
  export * from './hand.js';
10
+ export * from './evolve-events.js';
@@ -7,3 +7,4 @@ export * from './cache-hit.js';
7
7
  export * from './agent-lifecycle.js';
8
8
  export * from './proc-tap.js';
9
9
  export * from './hand.js';
10
+ export * from './evolve-events.js';
@@ -65,11 +65,13 @@ export function expandTools(tools) {
65
65
  export function flattenPayload(payload) {
66
66
  const { _input_messages, _output_messages, _tools, ...rest } = payload;
67
67
  const flat = { ...rest };
68
- if (_input_messages?.length) {
69
- Object.assign(flat, expandMessages('llm.input_messages', _input_messages));
70
- const lastUser = [..._input_messages].reverse().find((m) => m['message.role'] === 'user');
71
- if (lastUser?.['message.content'])
72
- flat['input.value'] = lastUser['message.content'];
68
+ if (_input_messages) {
69
+ if (_input_messages.length) {
70
+ Object.assign(flat, expandMessages('llm.input_messages', _input_messages));
71
+ const lastUser = [..._input_messages].reverse().find((m) => m['message.role'] === 'user');
72
+ if (lastUser?.['message.content'])
73
+ flat['input.value'] = lastUser['message.content'];
74
+ }
73
75
  }
74
76
  if (_output_messages?.length) {
75
77
  Object.assign(flat, expandMessages('llm.output_messages', _output_messages));
@@ -1,21 +1,22 @@
1
1
  export interface MemorySearchPayload {
2
2
  'openinference.span.kind': 'RETRIEVER';
3
3
  'input.value'?: string;
4
- 'retrieval.documents'?: unknown[];
5
4
  'enact.query_hash'?: string;
6
5
  'enact.results_count'?: number;
7
6
  'enact.duration_ms'?: number;
7
+ [k: `retrieval.documents.${number}.document.${'id' | 'content' | 'score' | 'metadata'}`]: string | number | undefined;
8
8
  }
9
9
  export interface MemoryWritePayload {
10
10
  'openinference.span.kind': 'CHAIN';
11
11
  'enact.slug'?: string;
12
- 'enact.memory_type'?: string;
12
+ 'enact.wiki_type'?: string;
13
13
  'enact.importance'?: number;
14
14
  'input.value'?: string;
15
+ 'output.value'?: string;
15
16
  }
16
17
  export interface MemoryLifecyclePayload {
17
18
  'openinference.span.kind': 'CHAIN';
18
19
  lifecycle_event: string;
19
- 'enact.memory_type'?: string;
20
+ 'enact.wiki_type'?: string;
20
21
  [key: string]: unknown;
21
22
  }
@@ -1,5 +1,5 @@
1
- import type { LlmCallPayload, HookFiredPayload, ToolCallPayload, MemorySearchPayload, MemoryWritePayload, CacheHitPayload, AgentLifecyclePayload } from '../payloads/index.js';
2
- export type EventPayload = LlmCallPayload | HookFiredPayload | ToolCallPayload | MemorySearchPayload | MemoryWritePayload | CacheHitPayload | AgentLifecyclePayload | Record<string, unknown>;
1
+ import type { LlmCallPayload, HookFiredPayload, ToolCallPayload, MemorySearchPayload, MemoryWritePayload, CacheHitPayload, AgentLifecyclePayload, EvolveEventPayload } from '../payloads/index.js';
2
+ export type EventPayload = LlmCallPayload | HookFiredPayload | ToolCallPayload | MemorySearchPayload | MemoryWritePayload | CacheHitPayload | AgentLifecyclePayload | EvolveEventPayload | Record<string, unknown>;
3
3
  export interface EnactRawV1Event {
4
4
  schema_version: 'enact.raw.v1';
5
5
  source: string;
@@ -11,5 +11,19 @@ export interface EnactRawV1Event {
11
11
  pid?: number;
12
12
  parent_pid?: number;
13
13
  correlation_id?: string;
14
+ trace_id?: string;
15
+ parent_span_id?: string;
14
16
  payload: EventPayload;
15
17
  }
18
+ /**
19
+ * Subset of payload fields projected by build_envelope that are always
20
+ * present at the payload level for OpenInference consumers (not the envelope root).
21
+ */
22
+ export interface EnactPayloadEnrichments {
23
+ /** OpenInference canonical session identifier. */
24
+ 'session.id'?: string;
25
+ /** OpenInference canonical user identifier — projected from EmitContext.user_id. */
26
+ 'user.id'?: string;
27
+ /** OpenInference span classification. */
28
+ 'openinference.span.kind'?: string;
29
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@amsterdamdatalabs/traces",
3
- "version": "0.1.0",
3
+ "version": "0.1.3",
4
4
  "description": "Shared OpenInference tracing types and emit for enact-os packages",
5
5
  "license": "UNLICENSED",
6
6
  "type": "module",
@@ -34,14 +34,18 @@
34
34
  "scripts": {
35
35
  "build": "rm -rf dist && tsc",
36
36
  "dev": "tsc --watch",
37
- "lint": "oxlint src/"
37
+ "lint": "oxlint src/",
38
+ "test": "jest"
38
39
  },
39
40
  "dependencies": {
40
41
  "@arizeai/openinference-semantic-conventions": "^2.3.0"
41
42
  },
42
43
  "devDependencies": {
44
+ "@types/jest": "^30.0.0",
43
45
  "@types/node": "^25.9.0",
46
+ "jest": "^30.4.2",
44
47
  "oxlint": "^0.13.0",
48
+ "ts-jest": "^29.4.11",
45
49
  "typescript": "^5.4.5"
46
50
  }
47
51
  }