promptlayer 1.2.0 → 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.
- package/dist/claude-agents.js +1 -1
- package/dist/claude-agents.js.map +1 -1
- package/dist/esm/{chunk-DFBRFJOL.js → chunk-7Y65WGSZ.js} +2 -2
- package/dist/esm/claude-agents.js +1 -1
- package/dist/esm/claude-agents.js.map +1 -1
- package/dist/esm/index.js +1 -1
- package/dist/esm/openai-agents.js +1 -1
- package/dist/index.js +1 -1
- package/dist/openai-agents.js +1 -1
- package/package.json +1 -1
- package/vendor/claude-agents/trace/hooks/lib.sh +4 -522
- package/vendor/claude-agents/trace/hooks/post_tool_use.sh +2 -27
- package/vendor/claude-agents/trace/hooks/py/__init__.py +1 -0
- package/vendor/claude-agents/trace/hooks/py/cli.py +81 -0
- package/vendor/claude-agents/trace/hooks/py/context.py +63 -0
- package/vendor/claude-agents/trace/hooks/py/handlers.py +244 -0
- package/vendor/claude-agents/trace/hooks/py/otlp.py +278 -0
- package/vendor/claude-agents/trace/hooks/py/settings.py +33 -0
- package/vendor/claude-agents/trace/hooks/py/state.py +135 -0
- package/vendor/claude-agents/trace/hooks/{parse_stop_transcript.py → py/stop_parser.py} +69 -31
- package/vendor/claude-agents/trace/hooks/py/traceparent.py +31 -0
- package/vendor/claude-agents/trace/hooks/session_end.sh +1 -23
- package/vendor/claude-agents/trace/hooks/session_start.sh +5 -41
- package/vendor/claude-agents/trace/hooks/stop_hook.sh +3 -106
- package/vendor/claude-agents/trace/hooks/user_prompt_submit.sh +1 -11
- package/vendor/claude-agents/trace/setup.sh +170 -0
- package/vendor/claude-agents/vendor_metadata.json +2 -2
- package/vendor/claude-agents/trace/hooks/hook_utils.py +0 -38
- /package/dist/esm/{chunk-DFBRFJOL.js.map → chunk-7Y65WGSZ.js.map} +0 -0
|
@@ -47,531 +47,13 @@ tracing_enabled() {
|
|
|
47
47
|
}
|
|
48
48
|
|
|
49
49
|
check_requirements() {
|
|
50
|
-
|
|
51
|
-
|
|
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
|
-
|
|
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 "$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,
|
|
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
|
-
|
|
13
|
-
|
|
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())
|