@contractspec/lib.ai-agent 2.0.0 → 2.1.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.
@@ -2536,24 +2536,78 @@ function generateSessionId() {
2536
2536
 
2537
2537
  // src/telemetry/adapter.ts
2538
2538
  function parseAgentId(agentId) {
2539
- const match = agentId.match(/^(.+)\.v(\s+)$/);
2539
+ const match = agentId.match(/^(.+)\.v(\d+)$/);
2540
2540
  if (match) {
2541
2541
  return { name: match[1], version: match[2] };
2542
2542
  }
2543
2543
  return { name: agentId, version: "1.0.0" };
2544
2544
  }
2545
- async function trackAgentStep(collector, agentId, step, durationMs) {
2545
+ function getRecord(value) {
2546
+ return value && typeof value === "object" ? value : undefined;
2547
+ }
2548
+ function getStepResponse(step) {
2549
+ const response = getRecord(step.response);
2550
+ if (!response)
2551
+ return;
2552
+ return {
2553
+ id: response["id"],
2554
+ modelId: response["modelId"],
2555
+ timestamp: response["timestamp"],
2556
+ headers: response["headers"],
2557
+ body: response["body"],
2558
+ messages: response["messages"]
2559
+ };
2560
+ }
2561
+ function getRequestBodyValue(request, key) {
2562
+ const body = request?.["body"];
2563
+ if (!body)
2564
+ return;
2565
+ if (typeof body === "string") {
2566
+ try {
2567
+ const parsed = JSON.parse(body);
2568
+ return typeof parsed[key] === "string" ? parsed[key] : undefined;
2569
+ } catch {
2570
+ return;
2571
+ }
2572
+ }
2573
+ const record = getRecord(body);
2574
+ return typeof record?.[key] === "string" ? record[key] : undefined;
2575
+ }
2576
+ async function trackAgentStep(collector, agentId, step, durationMs, context) {
2546
2577
  const { name, version } = parseAgentId(agentId);
2578
+ const response = getStepResponse(step);
2579
+ const providerMetadata = step.providerMetadata;
2580
+ const warnings = step.warnings;
2581
+ const rawFinishReason = step.rawFinishReason;
2582
+ const request = getRecord(step.request);
2583
+ const traceId = context?.traceId ?? getRequestBodyValue(request, "$ai_trace_id");
2584
+ const sessionId = context?.sessionId ?? getRequestBodyValue(request, "$ai_session_id");
2585
+ const parentSpanId = response?.id;
2547
2586
  for (const toolCall of step.toolCalls ?? []) {
2587
+ const toolResult = step.toolResults?.find((r) => r.toolCallId === toolCall.toolCallId);
2588
+ const toolError = getRecord(toolResult?.output)?.["error"];
2548
2589
  const toolSample = {
2549
2590
  operation: { name: `${name}.${toolCall.toolName}`, version },
2550
2591
  durationMs: durationMs ?? 0,
2551
2592
  success: step.toolResults?.some((r) => r.toolCallId === toolCall.toolCallId && r.output !== undefined) ?? false,
2552
2593
  timestamp: new Date,
2553
2594
  metadata: {
2595
+ telemetryEvent: "span",
2596
+ traceId,
2597
+ sessionId,
2598
+ spanId: toolCall.toolCallId,
2599
+ parentSpanId,
2600
+ spanName: `tool.${toolCall.toolName}`,
2554
2601
  agentId,
2602
+ actorId: context?.actorId,
2603
+ tenantId: context?.tenantId,
2604
+ stepIndex: context?.stepIndex,
2555
2605
  toolName: toolCall.toolName,
2556
- finishReason: step.finishReason
2606
+ toolCallArgs: toolCall.input,
2607
+ toolResultOutput: toolResult?.output,
2608
+ errorMessage: typeof toolError === "string" ? toolError : typeof toolResult?.output === "string" ? toolResult.output : undefined,
2609
+ finishReason: step.finishReason,
2610
+ rawFinishReason
2557
2611
  }
2558
2612
  };
2559
2613
  await collector.collect(toolSample);
@@ -2564,10 +2618,36 @@ async function trackAgentStep(collector, agentId, step, durationMs) {
2564
2618
  success: step.finishReason !== "error",
2565
2619
  timestamp: new Date,
2566
2620
  metadata: {
2621
+ telemetryEvent: "generation",
2622
+ traceId,
2623
+ sessionId,
2624
+ spanId: response?.id,
2625
+ spanName: `agent.${name}.step`,
2567
2626
  agentId,
2627
+ actorId: context?.actorId,
2628
+ tenantId: context?.tenantId,
2629
+ stepIndex: context?.stepIndex,
2630
+ stepStartedAt: context?.stepStartedAt,
2568
2631
  finishReason: step.finishReason,
2632
+ rawFinishReason,
2569
2633
  tokenUsage: step.usage,
2570
- toolCallCount: step.toolCalls?.length ?? 0
2634
+ totalUsage: step.totalUsage,
2635
+ providerMetadata,
2636
+ warnings,
2637
+ stepText: step.text,
2638
+ stepReasoningText: step.reasoningText,
2639
+ responseId: response?.id,
2640
+ responseModelId: response?.modelId,
2641
+ responseTimestamp: response?.timestamp,
2642
+ responseHeaders: response?.headers,
2643
+ responseBody: response?.body,
2644
+ responseMessages: response?.messages,
2645
+ requestBody: request?.["body"],
2646
+ requestHeaders: request?.["headers"],
2647
+ toolCallCount: step.toolCalls?.length ?? 0,
2648
+ toolCalls: step.toolCalls,
2649
+ toolResults: step.toolResults,
2650
+ errorMessage: step.finishReason === "error" ? step.text : undefined
2571
2651
  }
2572
2652
  };
2573
2653
  await collector.collect(stepSample);
@@ -2627,23 +2707,60 @@ class PostHogTelemetryCollector {
2627
2707
  }
2628
2708
  async collect(sample) {
2629
2709
  const client = await this.getClient();
2630
- const distinctId = this.config.defaults?.posthogDistinctId ?? sample.metadata?.["actorId"] ?? "system";
2710
+ const metadata = asRecord(sample.metadata);
2711
+ const distinctId = this.config.defaults?.posthogDistinctId ?? asString(metadata["actorId"]) ?? "system";
2712
+ const traceId = asString(metadata["traceId"]) ?? this.config.defaults?.posthogTraceId;
2713
+ const sessionId = asString(metadata["sessionId"]);
2714
+ const telemetryEvent = asString(metadata["telemetryEvent"]);
2715
+ const event = telemetryEvent === "span" ? "$ai_span" : "$ai_generation";
2716
+ const tokenUsage = metadata["tokenUsage"];
2717
+ const totalUsage = metadata["totalUsage"];
2718
+ const errorMessage = asString(metadata["errorMessage"]);
2631
2719
  client.capture({
2632
2720
  distinctId,
2633
- event: "$ai_generation",
2721
+ event,
2634
2722
  properties: {
2635
- $ai_model: sample.operation.name,
2636
- $ai_provider: "contractspec",
2723
+ $ai_model: asString(metadata["responseModelId"]) ?? sample.operation.name,
2724
+ $ai_provider: asString(metadata["provider"]) ?? "contractspec",
2637
2725
  $ai_latency: sample.durationMs / 1000,
2638
2726
  $ai_is_error: !sample.success,
2639
- $ai_trace_id: this.config.defaults?.posthogTraceId,
2640
- ...sample.metadata?.["tokenUsage"] ? mapTokenUsage(sample.metadata["tokenUsage"]) : {},
2727
+ $ai_error: !sample.success ? errorMessage : undefined,
2728
+ $ai_trace_id: traceId,
2729
+ $ai_session_id: sessionId,
2730
+ $ai_span_id: asString(metadata["spanId"]),
2731
+ $ai_parent_id: asString(metadata["parentSpanId"]),
2732
+ $ai_span_name: asString(metadata["spanName"]) ?? sample.operation.name,
2733
+ ...tokenUsage ? mapTokenUsage(tokenUsage) : {},
2734
+ ...totalUsage ? mapTotalUsage(totalUsage) : {},
2735
+ $ai_http_status: asNumber(metadata["httpStatus"]),
2736
+ $ai_request_url: asString(metadata["requestUrl"]),
2737
+ $ai_base_url: asString(metadata["baseUrl"]),
2641
2738
  ...this.config.defaults?.posthogProperties,
2642
2739
  contractspec_operation: sample.operation.name,
2643
2740
  contractspec_version: sample.operation.version,
2644
- contractspec_agent_id: sample.metadata?.["agentId"],
2645
- contractspec_finish_reason: sample.metadata?.["finishReason"],
2646
- contractspec_tool_count: sample.metadata?.["toolCallCount"]
2741
+ contractspec_agent_id: asString(metadata["agentId"]),
2742
+ contractspec_tenant_id: asString(metadata["tenantId"]),
2743
+ contractspec_actor_id: asString(metadata["actorId"]),
2744
+ contractspec_step_index: asNumber(metadata["stepIndex"]),
2745
+ contractspec_step_started_at: asDateIso(metadata["stepStartedAt"]),
2746
+ contractspec_finish_reason: asString(metadata["finishReason"]),
2747
+ contractspec_finish_reason_raw: asString(metadata["rawFinishReason"]),
2748
+ contractspec_tool_count: asNumber(metadata["toolCallCount"]),
2749
+ contractspec_tool_name: asString(metadata["toolName"]),
2750
+ contractspec_tool_call_args: metadata["toolCallArgs"],
2751
+ contractspec_tool_result_output: metadata["toolResultOutput"],
2752
+ contractspec_provider_metadata: metadata["providerMetadata"],
2753
+ contractspec_step_warnings: metadata["warnings"],
2754
+ contractspec_response_id: asString(metadata["responseId"]),
2755
+ contractspec_response_model_id: asString(metadata["responseModelId"]),
2756
+ contractspec_response_timestamp: asDateIso(metadata["responseTimestamp"]),
2757
+ contractspec_response_headers: metadata["responseHeaders"],
2758
+ contractspec_response_body: this.config.defaults?.posthogPrivacyMode ? undefined : metadata["responseBody"],
2759
+ contractspec_response_messages: this.config.defaults?.posthogPrivacyMode ? undefined : metadata["responseMessages"],
2760
+ contractspec_request_headers: metadata["requestHeaders"],
2761
+ contractspec_request_body: this.config.defaults?.posthogPrivacyMode ? undefined : metadata["requestBody"],
2762
+ contractspec_step_text: this.config.defaults?.posthogPrivacyMode ? undefined : metadata["stepText"],
2763
+ contractspec_step_reasoning_text: this.config.defaults?.posthogPrivacyMode ? undefined : metadata["stepReasoningText"]
2647
2764
  },
2648
2765
  groups: this.config.defaults?.posthogGroups
2649
2766
  });
@@ -2704,11 +2821,44 @@ async function resolvePostHogClient(config) {
2704
2821
  }
2705
2822
  }
2706
2823
  function mapTokenUsage(usage) {
2824
+ const usageRecord = asRecord(usage);
2825
+ const inputTokenDetails = asRecord(usageRecord["inputTokenDetails"]);
2826
+ const outputTokenDetails = asRecord(usageRecord["outputTokenDetails"]);
2707
2827
  return {
2708
- $ai_input_tokens: usage["promptTokens"],
2709
- $ai_output_tokens: usage["completionTokens"]
2828
+ $ai_input_tokens: asNumber(usageRecord["inputTokens"]) ?? asNumber(usageRecord["promptTokens"]),
2829
+ $ai_output_tokens: asNumber(usageRecord["outputTokens"]) ?? asNumber(usageRecord["completionTokens"]),
2830
+ $ai_reasoning_tokens: asNumber(outputTokenDetails["reasoningTokens"]) ?? asNumber(usageRecord["reasoningTokens"]),
2831
+ $ai_cache_read_input_tokens: asNumber(inputTokenDetails["cacheReadTokens"]) ?? asNumber(usageRecord["cachedInputTokens"]),
2832
+ $ai_cache_creation_input_tokens: asNumber(inputTokenDetails["cacheWriteTokens"]),
2833
+ $ai_usage: maybeRecord(usageRecord["raw"]) ?? usageRecord
2710
2834
  };
2711
2835
  }
2836
+ function mapTotalUsage(usage) {
2837
+ const usageRecord = asRecord(usage);
2838
+ return {
2839
+ contractspec_total_input_tokens: asNumber(usageRecord["inputTokens"]),
2840
+ contractspec_total_output_tokens: asNumber(usageRecord["outputTokens"]),
2841
+ contractspec_total_tokens: asNumber(usageRecord["totalTokens"]),
2842
+ contractspec_total_usage_raw: maybeRecord(usageRecord["raw"]) ?? usageRecord
2843
+ };
2844
+ }
2845
+ function asRecord(value) {
2846
+ return value && typeof value === "object" ? value : {};
2847
+ }
2848
+ function maybeRecord(value) {
2849
+ return value && typeof value === "object" ? value : undefined;
2850
+ }
2851
+ function asString(value) {
2852
+ return typeof value === "string" && value.length > 0 ? value : undefined;
2853
+ }
2854
+ function asNumber(value) {
2855
+ return typeof value === "number" ? value : undefined;
2856
+ }
2857
+ function asDateIso(value) {
2858
+ if (value instanceof Date)
2859
+ return value.toISOString();
2860
+ return;
2861
+ }
2712
2862
 
2713
2863
  // src/agent/contract-spec-agent.ts
2714
2864
  var exports_contract_spec_agent = {};
@@ -2719,6 +2869,7 @@ import {
2719
2869
  Experimental_Agent as ToolLoopAgent,
2720
2870
  stepCountIs
2721
2871
  } from "ai";
2872
+ import { randomUUID } from "node:crypto";
2722
2873
  import * as z3 from "zod";
2723
2874
 
2724
2875
  class ContractSpecAgent {
@@ -2726,33 +2877,18 @@ class ContractSpecAgent {
2726
2877
  id;
2727
2878
  spec;
2728
2879
  tools;
2729
- inner;
2730
2880
  config;
2731
2881
  instructions;
2882
+ activeStepContexts = new Map;
2732
2883
  constructor(config, instructions, tools) {
2733
2884
  this.config = config;
2734
2885
  this.spec = config.spec;
2735
2886
  this.id = agentKey(config.spec.meta);
2736
2887
  this.tools = tools;
2737
2888
  this.instructions = instructions;
2738
- this.inner = new ToolLoopAgent({
2739
- model: config.model,
2740
- instructions,
2741
- tools,
2742
- stopWhen: stepCountIs(config.spec.maxSteps ?? 10),
2743
- callOptionsSchema: ContractSpecCallOptionsSchema,
2744
- onStepFinish: async (step) => {
2745
- await this.handleStepFinish(step);
2746
- }
2747
- });
2748
2889
  }
2749
2890
  static async create(config) {
2750
- let effectiveConfig = config;
2751
- if (config.posthogConfig) {
2752
- const { createPostHogTracedModel: createPostHogTracedModel2 } = await Promise.resolve().then(() => exports_posthog);
2753
- const tracedModel = await createPostHogTracedModel2(config.model, config.posthogConfig, config.posthogConfig.tracingOptions);
2754
- effectiveConfig = { ...config, model: tracedModel };
2755
- }
2891
+ const effectiveConfig = config;
2756
2892
  const instructions = await injectStaticKnowledge(effectiveConfig.spec.instructions, effectiveConfig.spec.knowledge ?? [], effectiveConfig.knowledgeRetriever);
2757
2893
  const specTools = specToolsToAISDKTools(effectiveConfig.spec.tools, effectiveConfig.toolHandlers, { agentId: agentKey(effectiveConfig.spec.meta) });
2758
2894
  const knowledgeTool = effectiveConfig.knowledgeRetriever ? createKnowledgeQueryTool(effectiveConfig.knowledgeRetriever, effectiveConfig.spec.knowledge ?? []) : null;
@@ -2765,6 +2901,14 @@ class ContractSpecAgent {
2765
2901
  }
2766
2902
  async generate(params) {
2767
2903
  const sessionId = params.options?.sessionId ?? generateSessionId();
2904
+ const traceId = params.options?.metadata?.["traceId"] ?? this.config.posthogConfig?.tracingOptions?.posthogTraceId ?? randomUUID();
2905
+ this.activeStepContexts.set(sessionId, {
2906
+ traceId,
2907
+ tenantId: params.options?.tenantId,
2908
+ actorId: params.options?.actorId,
2909
+ stepIndex: 0,
2910
+ stepStartedAt: new Date
2911
+ });
2768
2912
  if (this.config.sessionStore) {
2769
2913
  const existing = await this.config.sessionStore.get(sessionId);
2770
2914
  if (!existing) {
@@ -2785,7 +2929,13 @@ class ContractSpecAgent {
2785
2929
  ${params.systemOverride}
2786
2930
 
2787
2931
  ${params.prompt}` : params.prompt;
2788
- const result = await this.inner.generate({
2932
+ const model = await this.resolveModelForCall({
2933
+ sessionId,
2934
+ traceId,
2935
+ options: params.options
2936
+ });
2937
+ const inner = this.createInnerAgent(model);
2938
+ const result = await inner.generate({
2789
2939
  prompt,
2790
2940
  abortSignal: params.signal,
2791
2941
  options: {
@@ -2794,6 +2944,8 @@ ${params.prompt}` : params.prompt;
2794
2944
  sessionId,
2795
2945
  metadata: params.options?.metadata
2796
2946
  }
2947
+ }).finally(() => {
2948
+ this.activeStepContexts.delete(sessionId);
2797
2949
  });
2798
2950
  if (this.config.sessionStore) {
2799
2951
  await this.config.sessionStore.update(sessionId, {
@@ -2821,12 +2973,26 @@ ${params.prompt}` : params.prompt;
2821
2973
  }
2822
2974
  async stream(params) {
2823
2975
  const sessionId = params.options?.sessionId ?? generateSessionId();
2976
+ const traceId = params.options?.metadata?.["traceId"] ?? this.config.posthogConfig?.tracingOptions?.posthogTraceId ?? randomUUID();
2977
+ this.activeStepContexts.set(sessionId, {
2978
+ traceId,
2979
+ tenantId: params.options?.tenantId,
2980
+ actorId: params.options?.actorId,
2981
+ stepIndex: 0,
2982
+ stepStartedAt: new Date
2983
+ });
2824
2984
  const prompt = params.systemOverride ? `${this.instructions}
2825
2985
 
2826
2986
  ${params.systemOverride}
2827
2987
 
2828
2988
  ${params.prompt}` : params.prompt;
2829
- return this.inner.stream({
2989
+ const model = await this.resolveModelForCall({
2990
+ sessionId,
2991
+ traceId,
2992
+ options: params.options
2993
+ });
2994
+ const inner = this.createInnerAgent(model);
2995
+ return inner.stream({
2830
2996
  prompt,
2831
2997
  abortSignal: params.signal,
2832
2998
  options: {
@@ -2843,9 +3009,63 @@ ${params.prompt}` : params.prompt;
2843
3009
  await this.config.sessionStore.appendStep(sessionId, step);
2844
3010
  }
2845
3011
  if (this.config.telemetryCollector) {
2846
- await trackAgentStep(this.config.telemetryCollector, this.id, step);
3012
+ const now = new Date;
3013
+ const context = sessionId ? this.activeStepContexts.get(sessionId) : undefined;
3014
+ const stepStartedAt = context?.stepStartedAt ?? now;
3015
+ const durationMs = Math.max(now.getTime() - stepStartedAt.getTime(), 0);
3016
+ if (context) {
3017
+ context.stepIndex += 1;
3018
+ context.stepStartedAt = now;
3019
+ }
3020
+ await trackAgentStep(this.config.telemetryCollector, this.id, step, durationMs, {
3021
+ sessionId,
3022
+ tenantId: context?.tenantId,
3023
+ actorId: context?.actorId,
3024
+ traceId: context?.traceId,
3025
+ stepIndex: context?.stepIndex,
3026
+ stepStartedAt
3027
+ });
3028
+ if (sessionId && step.finishReason !== "tool-calls") {
3029
+ this.activeStepContexts.delete(sessionId);
3030
+ }
2847
3031
  }
2848
3032
  }
3033
+ createInnerAgent(model) {
3034
+ return new ToolLoopAgent({
3035
+ model,
3036
+ instructions: this.instructions,
3037
+ tools: this.tools,
3038
+ stopWhen: stepCountIs(this.spec.maxSteps ?? 10),
3039
+ callOptionsSchema: ContractSpecCallOptionsSchema,
3040
+ onStepFinish: async (step) => {
3041
+ await this.handleStepFinish(step);
3042
+ }
3043
+ });
3044
+ }
3045
+ async resolveModelForCall(params) {
3046
+ const posthogConfig = this.config.posthogConfig;
3047
+ if (!posthogConfig) {
3048
+ return this.config.model;
3049
+ }
3050
+ const mergedProperties = {
3051
+ ...posthogConfig.defaults?.posthogProperties,
3052
+ ...posthogConfig.tracingOptions?.posthogProperties,
3053
+ $ai_session_id: params.sessionId,
3054
+ contractspec_session_id: params.sessionId,
3055
+ contractspec_trace_id: params.traceId,
3056
+ contractspec_agent_id: this.id,
3057
+ contractspec_tenant_id: params.options?.tenantId,
3058
+ contractspec_actor_id: params.options?.actorId
3059
+ };
3060
+ const tracingOptions = {
3061
+ ...posthogConfig.tracingOptions,
3062
+ posthogDistinctId: posthogConfig.tracingOptions?.posthogDistinctId ?? params.options?.actorId,
3063
+ posthogTraceId: params.traceId,
3064
+ posthogProperties: mergedProperties
3065
+ };
3066
+ const { createPostHogTracedModel: createPostHogTracedModel2 } = await Promise.resolve().then(() => exports_posthog);
3067
+ return createPostHogTracedModel2(this.config.model, posthogConfig, tracingOptions);
3068
+ }
2849
3069
  }
2850
3070
  var ContractSpecCallOptionsSchema;
2851
3071
  var init_contract_spec_agent = __esm(() => {