promptlayer 1.2.1 → 1.2.2

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.
Files changed (29) hide show
  1. package/dist/claude-agents.js +1 -1
  2. package/dist/claude-agents.js.map +1 -1
  3. package/dist/esm/{chunk-UKSCOWKT.js → chunk-7Y65WGSZ.js} +2 -2
  4. package/dist/esm/claude-agents.js +1 -1
  5. package/dist/esm/claude-agents.js.map +1 -1
  6. package/dist/esm/index.js +1 -1
  7. package/dist/esm/openai-agents.js +1 -1
  8. package/dist/index.js +1 -1
  9. package/dist/openai-agents.js +1 -1
  10. package/package.json +1 -1
  11. package/vendor/claude-agents/trace/hooks/lib.sh +4 -522
  12. package/vendor/claude-agents/trace/hooks/post_tool_use.sh +2 -27
  13. package/vendor/claude-agents/trace/hooks/py/__init__.py +1 -0
  14. package/vendor/claude-agents/trace/hooks/py/cli.py +81 -0
  15. package/vendor/claude-agents/trace/hooks/py/context.py +63 -0
  16. package/vendor/claude-agents/trace/hooks/py/handlers.py +244 -0
  17. package/vendor/claude-agents/trace/hooks/py/otlp.py +278 -0
  18. package/vendor/claude-agents/trace/hooks/py/settings.py +33 -0
  19. package/vendor/claude-agents/trace/hooks/py/state.py +135 -0
  20. package/vendor/claude-agents/trace/hooks/{parse_stop_transcript.py → py/stop_parser.py} +69 -31
  21. package/vendor/claude-agents/trace/hooks/py/traceparent.py +31 -0
  22. package/vendor/claude-agents/trace/hooks/session_end.sh +1 -23
  23. package/vendor/claude-agents/trace/hooks/session_start.sh +5 -41
  24. package/vendor/claude-agents/trace/hooks/stop_hook.sh +3 -106
  25. package/vendor/claude-agents/trace/hooks/user_prompt_submit.sh +1 -11
  26. package/vendor/claude-agents/trace/setup.sh +170 -0
  27. package/vendor/claude-agents/vendor_metadata.json +2 -2
  28. package/vendor/claude-agents/trace/hooks/hook_utils.py +0 -38
  29. /package/dist/esm/{chunk-UKSCOWKT.js.map → chunk-7Y65WGSZ.js.map} +0 -0
@@ -47,531 +47,13 @@ tracing_enabled() {
47
47
  }
48
48
 
49
49
  check_requirements() {
50
- local cmd
51
- for cmd in jq curl uuidgen python3; do
52
- if ! command -v "$cmd" >/dev/null 2>&1; then
53
- log "ERROR" "Missing required command: $cmd"
54
- return 1
55
- fi
56
- done
57
- if [[ -z "$PL_API_KEY" ]]; then
58
- log "ERROR" "PROMPTLAYER_API_KEY is not set"
59
- return 1
60
- fi
61
- return 0
62
- }
63
-
64
- generate_trace_id() {
65
- uuidgen | tr -d '-' | tr '[:upper:]' '[:lower:]'
66
- }
67
-
68
- generate_span_id() {
69
- uuidgen | tr -d '-' | tr '[:upper:]' '[:lower:]' | cut -c1-16
70
- }
71
-
72
- parse_traceparent() {
73
- local raw="${1:-}"
74
- [[ -z "$raw" ]] && return 1
75
-
76
- raw="$(printf '%s' "$raw" | tr '[:upper:]' '[:lower:]')"
77
-
78
- if [[ ! "$raw" =~ ^([0-9a-f]{2})-([0-9a-f]{32})-([0-9a-f]{16})-([0-9a-f]{2})(-.+)?$ ]]; then
79
- return 1
80
- fi
81
-
82
- local version="${BASH_REMATCH[1]}"
83
- local trace_id="${BASH_REMATCH[2]}"
84
- local parent_span_id="${BASH_REMATCH[3]}"
85
- local trace_flags="${BASH_REMATCH[4]}"
86
- local suffix="${BASH_REMATCH[5]:-}"
87
-
88
- if [[ "$version" == "ff" ]]; then
89
- return 1
90
- fi
91
- if [[ "$version" == "00" && -n "$suffix" ]]; then
92
- return 1
93
- fi
94
- if [[ "$trace_id" == "00000000000000000000000000000000" ]]; then
95
- return 1
96
- fi
97
- if [[ "$parent_span_id" == "0000000000000000" ]]; then
98
- return 1
99
- fi
100
-
101
- printf '%s %s %s %s\n' "$version" "$trace_id" "$parent_span_id" "$trace_flags"
102
- }
103
-
104
- load_initial_trace_context() {
105
- PL_INITIAL_TRACEPARENT_VERSION=""
106
- PL_INITIAL_TRACE_ID=""
107
- PL_INITIAL_PARENT_SPAN_ID=""
108
- PL_INITIAL_TRACE_FLAGS=""
109
- PL_INITIAL_TRACE_CONTEXT_SOURCE="generated"
110
-
111
- local raw="${PROMPTLAYER_TRACEPARENT:-}"
112
- if [[ -z "$raw" ]]; then
113
- return 0
114
- fi
115
-
116
- local parsed
117
- if ! parsed="$(parse_traceparent "$raw")"; then
118
- log "WARN" "Ignoring invalid PROMPTLAYER_TRACEPARENT"
50
+ if ! command -v python3 >/dev/null 2>&1; then
51
+ log "ERROR" "Missing required command: python3"
119
52
  return 1
120
53
  fi
121
-
122
- read -r PL_INITIAL_TRACEPARENT_VERSION PL_INITIAL_TRACE_ID PL_INITIAL_PARENT_SPAN_ID PL_INITIAL_TRACE_FLAGS <<<"$parsed"
123
- PL_INITIAL_TRACE_CONTEXT_SOURCE="external_traceparent"
124
- return 0
125
- }
126
-
127
- normalize_hex_id() {
128
- local raw="$1"
129
- local expected_len="$2"
130
- local fallback="$3"
131
- local label="$4"
132
-
133
- local clean
134
- clean="$(echo "$raw" | tr -cd '[:xdigit:]' | tr '[:upper:]' '[:lower:]')"
135
- if [[ -z "$clean" ]]; then
136
- clean="$fallback"
137
- fi
138
-
139
- if ((${#clean} > expected_len)); then
140
- clean="${clean:0:expected_len}"
141
- elif ((${#clean} < expected_len)); then
142
- clean="$(printf "%-${expected_len}s" "$clean" | tr ' ' '0')"
143
- fi
144
-
145
- if [[ "$clean" != "$raw" ]]; then
146
- log "WARN" "Normalized $label from '$raw' to '$clean'"
147
- fi
148
-
149
- echo "$clean"
150
- }
151
-
152
- hex_to_base64() {
153
- local hex="$1"
154
- python3 "$PL_HOOKS_DIR/hook_utils.py" hex_to_base64 "$hex"
155
- }
156
-
157
- now_ns() {
158
- python3 "$PL_HOOKS_DIR/hook_utils.py" now_ns
159
- }
160
-
161
- session_state_file() {
162
- echo "$PL_SESSION_STATE_DIR/$1.json"
163
- }
164
-
165
- acquire_session_lock() {
166
- local sid="$1"
167
- [[ -z "$sid" ]] && return 1
168
-
169
- local lock_dir="$PL_LOCK_DIR/$sid.lock"
170
- local attempts=0
171
- while ! mkdir "$lock_dir" 2>/dev/null; do
172
- attempts=$((attempts + 1))
173
- if ((attempts >= 250)); then
174
- log "ERROR" "Timed out waiting for session lock session_id=$sid"
175
- return 1
176
- fi
177
- sleep 0.02
178
- done
179
-
180
- export PL_HELD_SESSION_LOCK="$lock_dir"
181
- return 0
182
- }
183
-
184
- release_session_lock() {
185
- local lock_dir="${PL_HELD_SESSION_LOCK:-}"
186
- if [[ -n "$lock_dir" ]]; then
187
- rmdir "$lock_dir" 2>/dev/null || rm -rf "$lock_dir"
188
- unset PL_HELD_SESSION_LOCK
189
- fi
190
- return 0
191
- }
192
-
193
- acquire_queue_lock() {
194
- local lock_dir="$PL_LOCK_DIR/queue.lock"
195
- local attempts=0
196
- while ! mkdir "$lock_dir" 2>/dev/null; do
197
- attempts=$((attempts + 1))
198
- if ((attempts >= 250)); then
199
- log "ERROR" "Timed out waiting for queue lock"
200
- return 1
201
- fi
202
- sleep 0.02
203
- done
204
-
205
- export PL_HELD_QUEUE_LOCK="$lock_dir"
206
- return 0
207
- }
208
-
209
- release_queue_lock() {
210
- local lock_dir="${PL_HELD_QUEUE_LOCK:-}"
211
- if [[ -n "$lock_dir" ]]; then
212
- rmdir "$lock_dir" 2>/dev/null || rm -rf "$lock_dir"
213
- unset PL_HELD_QUEUE_LOCK
214
- fi
215
- return 0
216
- }
217
-
218
- get_session_state() {
219
- local sid="$1"
220
- local key="$2"
221
- local f
222
- f="$(session_state_file "$sid")"
223
- if [[ -f "$f" ]]; then
224
- jq -r ".${key} // empty" "$f" 2>/dev/null || true
225
- fi
226
- }
227
-
228
- set_session_state() {
229
- local sid="$1"
230
- local key="$2"
231
- local val="$3"
232
- local f
233
- f="$(session_state_file "$sid")"
234
- local current
235
- current='{}'
236
- if [[ -f "$f" ]]; then
237
- current="$(cat "$f")"
238
- fi
239
- echo "$current" | jq --arg k "$key" --arg v "$val" '.[$k] = $v' >"$f"
240
- }
241
-
242
- ensure_session_initialized() {
243
- local sid="$1"
244
- local requested_start_ns="${2:-}"
245
- [[ -z "$sid" ]] && return 1
246
-
247
- local trace_id session_span_id session_parent_span_id session_start_ns init_source pending_tool_calls
248
- trace_id="$(get_session_state "$sid" trace_id)"
249
- session_span_id="$(get_session_state "$sid" session_span_id)"
250
- session_parent_span_id="$(get_session_state "$sid" session_parent_span_id)"
251
- session_start_ns="$(get_session_state "$sid" session_start_ns)"
252
- init_source="$(get_session_state "$sid" session_init_source)"
253
- pending_tool_calls="$(get_session_state "$sid" pending_tool_calls)"
254
-
255
- # Normal path: SessionStart already created state.
256
- if [[ -n "$trace_id" && -n "$session_span_id" ]]; then
257
- if [[ -z "$session_start_ns" ]]; then
258
- [[ -z "$requested_start_ns" ]] && requested_start_ns="$(now_ns)"
259
- set_session_state "$sid" session_start_ns "$requested_start_ns"
260
- fi
261
- if [[ -z "$init_source" ]]; then
262
- set_session_state "$sid" session_init_source "unknown"
263
- fi
264
- if [[ -z "$pending_tool_calls" ]]; then
265
- set_session_state "$sid" pending_tool_calls "[]"
266
- fi
267
- if [[ -z "$session_parent_span_id" ]]; then
268
- set_session_state "$sid" session_parent_span_id ""
269
- fi
270
- if [[ -z "$(get_session_state "$sid" session_traceparent_version)" ]]; then
271
- set_session_state "$sid" session_traceparent_version ""
272
- fi
273
- if [[ -z "$(get_session_state "$sid" session_trace_flags)" ]]; then
274
- set_session_state "$sid" session_trace_flags ""
275
- fi
276
- if [[ -z "$(get_session_state "$sid" trace_context_source)" ]]; then
277
- set_session_state "$sid" trace_context_source "generated"
278
- fi
279
- return 0
280
- fi
281
-
282
- # Fallback path for SDK environments that do not surface SessionStart.
283
- [[ -z "$requested_start_ns" ]] && requested_start_ns="$(now_ns)"
284
- load_initial_trace_context || true
285
- [[ -z "$trace_id" ]] && trace_id="${PL_INITIAL_TRACE_ID:-}"
286
- [[ -z "$trace_id" ]] && trace_id="$(generate_trace_id)"
287
- [[ -z "$session_span_id" ]] && session_span_id="$(generate_span_id)"
288
-
289
- set_session_state "$sid" trace_id "$trace_id"
290
- set_session_state "$sid" session_span_id "$session_span_id"
291
- set_session_state "$sid" session_parent_span_id "${PL_INITIAL_PARENT_SPAN_ID:-}"
292
- set_session_state "$sid" session_start_ns "$requested_start_ns"
293
- set_session_state "$sid" current_turn_start_ns ""
294
- set_session_state "$sid" pending_tool_calls "[]"
295
- set_session_state "$sid" session_init_source "lazy_init"
296
- set_session_state "$sid" session_traceparent_version "${PL_INITIAL_TRACEPARENT_VERSION:-}"
297
- set_session_state "$sid" session_trace_flags "${PL_INITIAL_TRACE_FLAGS:-}"
298
- set_session_state "$sid" trace_context_source "${PL_INITIAL_TRACE_CONTEXT_SOURCE:-generated}"
299
-
300
- log "INFO" "Session initialized lazily session_id=$sid trace_id=$trace_id"
301
- }
302
-
303
- post_otlp_payload_file() {
304
- local payload_file="$1"
305
- local status response_file
306
- response_file="$(mktemp "${TMPDIR:-/tmp}/pl-otlp-response.XXXXXX")"
307
- status="$(curl -sS -o "$response_file" -w "%{http_code}" -X POST \
308
- -H "Content-Type: application/json" \
309
- -H "X-Api-Key: $PL_API_KEY" \
310
- -H "User-Agent: $PL_USER_AGENT" \
311
- --connect-timeout "$PL_OTLP_CONNECT_TIMEOUT" \
312
- --max-time "$PL_OTLP_MAX_TIME" \
313
- "$PL_OTLP_ENDPOINT" \
314
- -d @"$payload_file" || true)"
315
-
316
- if [[ "$status" != "200" ]]; then
317
- log "ERROR" "Failed OTLP export status=$status"
318
- rm -f "$response_file"
54
+ if [[ -z "$PL_API_KEY" ]]; then
55
+ log "ERROR" "PROMPTLAYER_API_KEY is not set"
319
56
  return 1
320
57
  fi
321
-
322
- # OTLP JSON can return 200 with partialSuccess and rejected spans.
323
- # Treat this as non-retryable (same payload is likely to be rejected again).
324
- if command -v jq >/dev/null 2>&1; then
325
- local rejected
326
- rejected="$(jq -r '.partialSuccess.rejectedSpans // 0' "$response_file" 2>/dev/null || echo 0)"
327
- if [[ "$rejected" != "0" ]]; then
328
- local message
329
- message="$(jq -r '.partialSuccess.errorMessage // "Unknown rejection"' "$response_file" 2>/dev/null || true)"
330
- log "ERROR" "OTLP partial success: rejected_spans=$rejected message=$message"
331
- rm -f "$response_file"
332
- return 2
333
- fi
334
- fi
335
-
336
- rm -f "$response_file"
337
- return 0
338
- }
339
-
340
- append_to_otlp_queue_file() {
341
- local payload_file="$1"
342
- acquire_queue_lock || return 1
343
- # Compact to single line to preserve ndjson format
344
- jq -c '.' "$payload_file" >>"$PL_QUEUE_FILE"
345
- chmod 600 "$PL_QUEUE_FILE" 2>/dev/null || true
346
- release_queue_lock
347
58
  return 0
348
59
  }
349
-
350
- drain_otlp_queue() {
351
- if [[ ! -s "$PL_QUEUE_FILE" ]]; then
352
- return 0
353
- fi
354
-
355
- local drain_limit="$PL_QUEUE_DRAIN_LIMIT"
356
- if [[ ! "$drain_limit" =~ ^[0-9]+$ ]]; then
357
- drain_limit=10
358
- fi
359
- if ((drain_limit <= 0)); then
360
- return 0
361
- fi
362
-
363
- acquire_queue_lock || return 0
364
-
365
- local -a queue_payloads
366
- mapfile -t queue_payloads <"$PL_QUEUE_FILE" || true
367
-
368
- local total max_attempts i rc replayed dropped retryable_fail fail_index
369
- total="${#queue_payloads[@]}"
370
- if ((total == 0)); then
371
- release_queue_lock
372
- return 0
373
- fi
374
-
375
- max_attempts=$((total < drain_limit ? total : drain_limit))
376
- replayed=0
377
- dropped=0
378
- retryable_fail=0
379
- fail_index=-1
380
-
381
- local queued_tmp
382
- queued_tmp="$(mktemp "${TMPDIR:-/tmp}/pl-otlp-queued.XXXXXX")"
383
-
384
- for ((i = 0; i < max_attempts; i++)); do
385
- if [[ -z "${queue_payloads[$i]}" ]]; then
386
- continue
387
- fi
388
-
389
- printf '%s' "${queue_payloads[$i]}" >"$queued_tmp"
390
-
391
- if post_otlp_payload_file "$queued_tmp"; then
392
- replayed=$((replayed + 1))
393
- continue
394
- else
395
- rc=$?
396
- fi
397
-
398
- if [[ "$rc" == "2" ]]; then
399
- dropped=$((dropped + 1))
400
- continue
401
- fi
402
-
403
- retryable_fail=1
404
- fail_index="$i"
405
- break
406
- done
407
-
408
- rm -f "$queued_tmp"
409
-
410
- local tmp remaining_start
411
- tmp="$(mktemp "${TMPDIR:-/tmp}/pl-otlp-queue.XXXXXX")"
412
- if ((retryable_fail == 1)); then
413
- remaining_start="$fail_index"
414
- else
415
- remaining_start="$max_attempts"
416
- fi
417
-
418
- for ((i = remaining_start; i < total; i++)); do
419
- printf '%s\n' "${queue_payloads[$i]}" >>"$tmp"
420
- done
421
-
422
- mv "$tmp" "$PL_QUEUE_FILE"
423
- chmod 600 "$PL_QUEUE_FILE" 2>/dev/null || true
424
- release_queue_lock
425
-
426
- if ((replayed > 0)); then
427
- log "INFO" "Drained queued OTLP payloads count=$replayed"
428
- fi
429
- if ((dropped > 0)); then
430
- log "WARN" "Dropped non-retryable queued OTLP payloads count=$dropped"
431
- fi
432
-
433
- return 0
434
- }
435
-
436
- send_otlp_payload_file() {
437
- local payload_file="$1"
438
- local rc
439
-
440
- drain_otlp_queue || true
441
-
442
- if post_otlp_payload_file "$payload_file"; then
443
- return 0
444
- else
445
- rc=$?
446
- fi
447
-
448
- if [[ "$rc" == "1" ]]; then
449
- append_to_otlp_queue_file "$payload_file" || log "ERROR" "Failed to append OTLP payload to queue"
450
- fi
451
- return 1
452
- }
453
-
454
- kind_int_to_string() {
455
- case "$1" in
456
- 0) echo "SPAN_KIND_UNSPECIFIED" ;;
457
- 1) echo "SPAN_KIND_INTERNAL" ;;
458
- 2) echo "SPAN_KIND_SERVER" ;;
459
- 3) echo "SPAN_KIND_CLIENT" ;;
460
- 4) echo "SPAN_KIND_PRODUCER" ;;
461
- 5) echo "SPAN_KIND_CONSUMER" ;;
462
- *) echo "SPAN_KIND_UNSPECIFIED" ;;
463
- esac
464
- }
465
-
466
- build_span_json() {
467
- local trace_id="$1"
468
- local span_id="$2"
469
- local parent_span_id="$3"
470
- local name="$4"
471
- local kind="$5"
472
- local start_ns="$6"
473
- local end_ns="$7"
474
- local attrs_json="$8"
475
-
476
- # Convert integer kind to protobuf JSON enum string if needed
477
- if [[ "$kind" =~ ^[0-9]+$ ]]; then
478
- kind="$(kind_int_to_string "$kind")"
479
- fi
480
-
481
- trace_id="$(normalize_hex_id "$trace_id" 32 "$(generate_trace_id)" "trace_id")"
482
- span_id="$(normalize_hex_id "$span_id" 16 "$(generate_span_id)" "span_id")"
483
- if [[ -n "$parent_span_id" ]]; then
484
- parent_span_id="$(normalize_hex_id "$parent_span_id" 16 "$(generate_span_id)" "parent_span_id")"
485
- fi
486
-
487
- local trace_id_b64 span_id_b64 parent_span_id_b64
488
- trace_id_b64="$(hex_to_base64 "$trace_id")"
489
- span_id_b64="$(hex_to_base64 "$span_id")"
490
- parent_span_id_b64=""
491
- if [[ -n "$parent_span_id" ]]; then
492
- parent_span_id_b64="$(hex_to_base64 "$parent_span_id")"
493
- fi
494
-
495
- local span_json
496
- span_json="$(jq -cn \
497
- --arg trace_id "$trace_id_b64" \
498
- --arg span_id "$span_id_b64" \
499
- --arg parent_span_id "$parent_span_id_b64" \
500
- --arg name "$name" \
501
- --arg kind "$kind" \
502
- --arg start "$start_ns" \
503
- --arg end_time "$end_ns" \
504
- --argjson attributes "$attrs_json" \
505
- '{
506
- traceId: $trace_id,
507
- spanId: $span_id,
508
- parentSpanId: (if $parent_span_id == "" then null else $parent_span_id end),
509
- name: $name,
510
- kind: $kind,
511
- startTimeUnixNano: $start,
512
- endTimeUnixNano: $end_time,
513
- attributes: (
514
- $attributes
515
- | to_entries
516
- | map(select(.value != null))
517
- | map(
518
- . as $kv
519
- | {
520
- key: $kv.key,
521
- value: (
522
- if ($kv.value | type) == "string" then
523
- {stringValue: $kv.value}
524
- elif ($kv.value | type) == "boolean" then
525
- {boolValue: $kv.value}
526
- elif ($kv.value | type) == "number" then
527
- if ($kv.value | floor) == $kv.value then
528
- {intValue: ($kv.value | tostring)}
529
- else
530
- {doubleValue: $kv.value}
531
- end
532
- else
533
- {stringValue: ($kv.value | tojson)}
534
- end
535
- )
536
- }
537
- )
538
- )
539
- }')"
540
-
541
- echo "$span_json"
542
- }
543
-
544
- emit_spans_batch_file() {
545
- local spans_file="$1"
546
- if [[ ! -s "$spans_file" ]]; then
547
- return 0
548
- fi
549
-
550
- local payload_file
551
- payload_file="$(mktemp "${TMPDIR:-/tmp}/pl-otlp-batch.XXXXXX")"
552
- jq -cs '{resourceSpans:[{resource:{attributes:[{key:"service.name",value:{stringValue:"claude-code"}}]},scopeSpans:[{spans:.}]}]}' "$spans_file" >"$payload_file"
553
- send_otlp_payload_file "$payload_file"
554
- local rc=$?
555
- rm -f "$payload_file"
556
- return $rc
557
- }
558
-
559
- emit_span() {
560
- local trace_id="$1"
561
- local span_id="$2"
562
- local parent_span_id="$3"
563
- local name="$4"
564
- local kind="$5"
565
- local start_ns="$6"
566
- local end_ns="$7"
567
- local attrs_json="$8"
568
-
569
- local span_json spans_file
570
- span_json="$(build_span_json "$trace_id" "$span_id" "$parent_span_id" "$name" "$kind" "$start_ns" "$end_ns" "$attrs_json")" || return 1
571
- spans_file="$(mktemp "${TMPDIR:-/tmp}/pl-otlp-span.XXXXXX")"
572
- printf '%s\n' "$span_json" >"$spans_file"
573
- emit_spans_batch_file "$spans_file"
574
- local rc=$?
575
- rm -f "$spans_file"
576
- return $rc
577
- }
@@ -9,33 +9,8 @@ tracing_enabled || exit 0
9
9
  check_requirements || exit 0
10
10
 
11
11
  input="$(cat)"
12
- session_id="$(echo "$input" | jq -r '.session_id // empty')"
13
- tool_name="$(echo "$input" | jq -r '.tool_name // empty')"
14
- tool_input="$(echo "$input" | jq -c '.tool_input // {}')"
15
- tool_output="$(echo "$input" | jq -c '.tool_response // .output // {}')"
12
+ result="$(printf '%s' "$input" | python3 "$SCRIPT_DIR/py/cli.py" post-tool-use)"
13
+ IFS=$'\t' read -r session_id tool_name <<<"$result"
16
14
  [[ -z "$session_id" || -z "$tool_name" ]] && exit 0
17
15
 
18
- ensure_session_initialized "$session_id"
19
-
20
- trace_id="$(get_session_state "$session_id" trace_id)"
21
- [[ -z "$trace_id" ]] && exit 0
22
- turn_start_ns="$(get_session_state "$session_id" current_turn_start_ns)"
23
- if [[ -z "$turn_start_ns" ]]; then
24
- set_session_state "$session_id" current_turn_start_ns "$(now_ns)"
25
- fi
26
-
27
- attrs="$(jq -nc \
28
- --arg source claude-code \
29
- --arg hook PostToolUse \
30
- --arg tool_name "$tool_name" \
31
- --argjson function_input "$tool_input" \
32
- --argjson function_output "$tool_output" \
33
- '{source:$source,hook:$hook,tool_name:$tool_name,node_type:"CODE_EXECUTION",function_input:$function_input,function_output:$function_output}')"
34
-
35
- pending_tool_calls="$(get_session_state "$session_id" pending_tool_calls)"
36
- [[ -z "$pending_tool_calls" ]] && pending_tool_calls='[]'
37
-
38
- pending_tool_calls="$(echo "$pending_tool_calls" | jq -c --argjson attrs "$attrs" '. + [$attrs]')"
39
- set_session_state "$session_id" pending_tool_calls "$pending_tool_calls"
40
-
41
16
  log "INFO" "PostToolUse captured session_id=$session_id tool=$tool_name"
@@ -0,0 +1 @@
1
+ # Python runtime package for trace plugin hooks.
@@ -0,0 +1,81 @@
1
+ #!/usr/bin/env python3
2
+
3
+ import os
4
+ import pathlib
5
+ import sys
6
+
7
+
8
+ THIS_DIR = pathlib.Path(__file__).resolve().parent
9
+ if str(THIS_DIR) not in sys.path:
10
+ sys.path.insert(0, str(THIS_DIR))
11
+
12
+ from context import load_context
13
+ from handlers import (
14
+ handle_parse_stop_transcript,
15
+ handle_post_tool_use,
16
+ handle_session_end,
17
+ handle_session_start,
18
+ handle_stop_hook,
19
+ handle_user_prompt_submit,
20
+ )
21
+ from otlp import generate_session_id, generate_span_id, generate_trace_id, probe_endpoint
22
+ from settings import write_settings_env
23
+
24
+
25
+ def read_stdin() -> str:
26
+ return sys.stdin.read()
27
+
28
+
29
+ def main() -> int:
30
+ if len(sys.argv) < 2:
31
+ raise SystemExit("usage: cli.py <command> [args]")
32
+
33
+ command = sys.argv[1]
34
+ if command in {"session-start", "user-prompt-submit", "post-tool-use", "session-end", "stop-hook"}:
35
+ ctx = load_context()
36
+ raw_input = read_stdin()
37
+ if command == "session-start":
38
+ output = handle_session_start(ctx, raw_input)
39
+ elif command == "user-prompt-submit":
40
+ output = handle_user_prompt_submit(ctx, raw_input)
41
+ elif command == "post-tool-use":
42
+ output = handle_post_tool_use(ctx, raw_input)
43
+ elif command == "session-end":
44
+ output = handle_session_end(ctx, raw_input)
45
+ else:
46
+ output = handle_stop_hook(ctx, raw_input)
47
+ if output:
48
+ print(output)
49
+ return 0
50
+
51
+ if command == "generate-trace-id":
52
+ print(generate_trace_id())
53
+ return 0
54
+ if command == "generate-span-id":
55
+ print(generate_span_id())
56
+ return 0
57
+ if command == "generate-session-id":
58
+ print(generate_session_id())
59
+ return 0
60
+ if command == "write-settings-env":
61
+ if len(sys.argv) != 6:
62
+ raise SystemExit("usage: cli.py write-settings-env <settings_file> <api_key> <endpoint> <debug>")
63
+ print(write_settings_env(sys.argv[2], sys.argv[3], sys.argv[4], sys.argv[5]))
64
+ return 0
65
+ if command == "probe-endpoint":
66
+ if len(sys.argv) != 4:
67
+ raise SystemExit("usage: cli.py probe-endpoint <endpoint> <api_key>")
68
+ print(probe_endpoint(sys.argv[2], sys.argv[3]))
69
+ return 0
70
+ if command == "parse-stop-transcript":
71
+ if len(sys.argv) not in {4, 5}:
72
+ raise SystemExit("usage: cli.py parse-stop-transcript <transcript_path> <turn_start_ns> [session_id]")
73
+ expected_session_id = sys.argv[4] if len(sys.argv) == 5 else None
74
+ print(handle_parse_stop_transcript(sys.argv[2], sys.argv[3], expected_session_id))
75
+ return 0
76
+
77
+ raise SystemExit(f"unknown command: {command}")
78
+
79
+
80
+ if __name__ == "__main__":
81
+ raise SystemExit(main())