@agenthippo-ai/agenthippo 1.110.34 → 1.110.35

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.
@@ -48,7 +48,7 @@ def _parse_attr_dict(value: Any) -> Any:
48
48
  return None
49
49
 
50
50
  def _allowlisted_headers(headers: dict) -> dict:
51
- return {k.lower(): v for (k, v) in headers.items() if isinstance(k, str) and k.lower() in _METADATA_HEADER_ALLOWLIST}
51
+ return {k.lower(): v for k, v in headers.items() if isinstance(k, str) and k.lower() in _METADATA_HEADER_ALLOWLIST}
52
52
 
53
53
  def _filtered_metadata_headers(value: Any) -> str | None:
54
54
  headers = _parse_attr_dict(value)
@@ -94,7 +94,7 @@ class _RedactingSpan:
94
94
 
95
95
  def set_attributes(self, attributes: Any) -> Any:
96
96
  if isinstance(attributes, dict):
97
- for (key, value) in attributes.items():
97
+ for key, value in attributes.items():
98
98
  self.set_attribute(key, value)
99
99
  return None
100
100
  return self._span.set_attributes(attributes)
@@ -102,7 +102,7 @@ class _RedactingSpan:
102
102
  def add_event(self, name: Any, attributes: Any=None, **kwargs: Any) -> Any:
103
103
  if isinstance(attributes, dict):
104
104
  filtered = {}
105
- for (key, value) in attributes.items():
105
+ for key, value in attributes.items():
106
106
  if key == _REQUESTER_METADATA_KEY:
107
107
  scrubbed = _scrub_requester_metadata(value)
108
108
  if scrubbed is not None:
@@ -453,7 +453,7 @@ def strip_empty_thinking_blocks(messages: list[Any]) -> list[Any]:
453
453
  if cleaned.get('role') == 'assistant' and 'reasoning_content' in cleaned:
454
454
  rc = cleaned['reasoning_content']
455
455
  if not rc or (isinstance(rc, str) and (not rc.strip())):
456
- cleaned = {k: v for (k, v) in cleaned.items() if k != 'reasoning_content'}
456
+ cleaned = {k: v for k, v in cleaned.items() if k != 'reasoning_content'}
457
457
  content = cleaned.get('content')
458
458
  if isinstance(content, list):
459
459
  original_len = len(content)
@@ -140,7 +140,7 @@ def run_query(duckdb_path, traces_dir, sql_after_cte, trace_filter_clause=''):
140
140
  def chart_daily_costs(plt, data, output_dir):
141
141
  if not data:
142
142
  return None
143
- (fig, ax) = plt.subplots()
143
+ fig, ax = plt.subplots()
144
144
  dates = [r['date'] for r in data]
145
145
  costs = [float(r['cost'] or 0) for r in data]
146
146
  display_dates = [d[5:] for d in dates]
@@ -162,7 +162,7 @@ def chart_daily_costs(plt, data, output_dir):
162
162
  def chart_model_breakdown(plt, data, output_dir):
163
163
  if not data:
164
164
  return None
165
- (fig, ax) = plt.subplots()
165
+ fig, ax = plt.subplots()
166
166
  models = [r['model'] or 'unknown' for r in data]
167
167
  costs = [float(r['cost'] or 0) for r in data]
168
168
  requests = [int(r['requests'] or 0) for r in data]
@@ -173,7 +173,7 @@ def chart_model_breakdown(plt, data, output_dir):
173
173
  name = name[:18] + '..'
174
174
  short_names.append(name)
175
175
  colors = ACCENT_COLORS[:len(models)]
176
- (wedges, texts, autotexts) = ax.pie(costs, labels=short_names, colors=colors, autopct='%1.0f%%', pctdistance=0.8, startangle=90, wedgeprops=dict(width=0.5, edgecolor=BG_COLOR, linewidth=2))
176
+ wedges, texts, autotexts = ax.pie(costs, labels=short_names, colors=colors, autopct='%1.0f%%', pctdistance=0.8, startangle=90, wedgeprops=dict(width=0.5, edgecolor=BG_COLOR, linewidth=2))
177
177
  for t in texts:
178
178
  t.set_color(FG_COLOR)
179
179
  t.set_fontsize(9)
@@ -192,7 +192,7 @@ def chart_model_breakdown(plt, data, output_dir):
192
192
  def chart_top_sessions(plt, data, output_dir):
193
193
  if not data:
194
194
  return None
195
- (fig, ax) = plt.subplots()
195
+ fig, ax = plt.subplots()
196
196
  data = data[:8]
197
197
  session_ids = [format_session_handle(r.get('session_id')) for r in data]
198
198
  costs = [float(r['cost'] or 0) for r in data]
@@ -206,7 +206,7 @@ def chart_top_sessions(plt, data, output_dir):
206
206
  ax.set_xlabel('Cost ($)')
207
207
  ax.set_title('Top Sessions by Cost')
208
208
  ax.xaxis.set_major_formatter(plt.FuncFormatter(lambda x, _: f'${x:.4f}'))
209
- for (i, (bar, req)) in enumerate(zip(bars, requests)):
209
+ for i, (bar, req) in enumerate(zip(bars, requests)):
210
210
  ax.text(bar.get_width() + max(costs) * 0.02, bar.get_y() + bar.get_height() / 2, f'{req} req', va='center', fontsize=8, color=FG_COLOR)
211
211
  plt.tight_layout()
212
212
  path = os.path.join(output_dir, 'top-sessions.png')
@@ -217,7 +217,7 @@ def chart_top_sessions(plt, data, output_dir):
217
217
  def chart_token_efficiency(plt, data, output_dir):
218
218
  if not data:
219
219
  return None
220
- (fig, ax) = plt.subplots()
220
+ fig, ax = plt.subplots()
221
221
  models = [r['model'] or 'unknown' for r in data]
222
222
  input_t = [int(r['input_tokens'] or 0) for r in data]
223
223
  output_t = [int(r['output_tokens'] or 0) for r in data]
@@ -250,7 +250,7 @@ def chart_token_efficiency(plt, data, output_dir):
250
250
  def chart_latency_distribution(plt, data, output_dir):
251
251
  if not data:
252
252
  return None
253
- (fig, ax) = plt.subplots()
253
+ fig, ax = plt.subplots()
254
254
  models = [r['model'] or 'unknown' for r in data]
255
255
  p50 = [float(r['p50'] or 0) for r in data]
256
256
  p95 = [float(r['p95'] or 0) for r in data]
@@ -281,13 +281,13 @@ def chart_latency_distribution(plt, data, output_dir):
281
281
  def chart_hourly_activity(plt, data, output_dir):
282
282
  if not data:
283
283
  return None
284
- (fig, ax) = plt.subplots()
284
+ fig, ax = plt.subplots()
285
285
  hours = [int(r['hour']) for r in data]
286
286
  counts = [int(r['count'] or 0) for r in data]
287
287
  costs = [float(r['cost'] or 0) for r in data]
288
288
  hour_counts = [0] * 24
289
289
  hour_costs = [0.0] * 24
290
- for (h, c, co) in zip(hours, counts, costs):
290
+ for h, c, co in zip(hours, counts, costs):
291
291
  if 0 <= h < 24:
292
292
  hour_counts[h] = c
293
293
  hour_costs[h] = co
@@ -342,7 +342,7 @@ def main():
342
342
  else:
343
343
  sys.stderr.write('[Dashboard] Indexed DB unavailable; falling back to JSONL mode\n')
344
344
  chart_dir_override = os.environ.get('AGENTIDE_CHART_DIR', '').strip()
345
- (conn, conn_error) = open_duckdb_connection(traces_dir, use_indexed_db, index_db_path, trace_filter_clause)
345
+ conn, conn_error = open_duckdb_connection(traces_dir, use_indexed_db, index_db_path, trace_filter_clause)
346
346
  if conn is None:
347
347
  print(json.dumps({'status': 'error', 'error': conn_error}))
348
348
  sys.exit(1)
@@ -137,7 +137,7 @@ def get_changed_files(conn, traces_dir: Path) -> list[Path]:
137
137
  changed: list[Path] = []
138
138
  for jsonl_file in sorted(traces_dir.glob('*.jsonl')):
139
139
  try:
140
- (mtime_ms, size_bytes) = get_file_stats(jsonl_file)
140
+ mtime_ms, size_bytes = get_file_stats(jsonl_file)
141
141
  except OSError:
142
142
  continue
143
143
  key = str(jsonl_file)
@@ -235,14 +235,14 @@ def _normalize_messages(payload: Any, default_role: str) -> list[dict[str, Any]]
235
235
  def _tool_call_id(session_id: str | None, trace_id: str, call_id: str | None, idx: int) -> str:
236
236
  if call_id:
237
237
  return call_id
238
- seed = f"{session_id or ''}|{trace_id}|tool|{idx}"
238
+ seed = f'{session_id or ''}|{trace_id}|tool|{idx}'
239
239
  return hashlib.sha1(seed.encode('utf-8')).hexdigest()
240
240
 
241
241
  def extract_messages_and_tools(conn) -> None:
242
242
  rows = conn.execute('\n SELECT\n model_call_id,\n trace_id,\n span_id,\n session_id,\n turn_id,\n request_id,\n start_ms,\n end_ms,\n status,\n error_type,\n error_message,\n input_messages_raw,\n output_messages_raw,\n span_tool_name,\n span_tool_call_id,\n span_tool_arguments_raw,\n span_tool_result_raw\n FROM tmp_model_calls\n ').fetchall()
243
243
  tool_results_by_id: dict[str, dict[str, Any]] = {}
244
244
  for row in rows:
245
- (model_call_id, trace_id, span_id, session_id, turn_id, request_id, start_ms, end_ms, call_status, call_error_type, call_error_message, input_raw, output_raw, span_tool_name, span_tool_call_id, span_tool_arguments_raw, span_tool_result_raw) = row
245
+ model_call_id, trace_id, span_id, session_id, turn_id, request_id, start_ms, end_ms, call_status, call_error_type, call_error_message, input_raw, output_raw, span_tool_name, span_tool_call_id, span_tool_arguments_raw, span_tool_result_raw = row
246
246
  tool_call_inserted = False
247
247
  tool_base_ts = int(end_ms) if end_ms is not None else int(start_ms) if start_ms is not None else 0
248
248
  if span_tool_name:
@@ -266,10 +266,10 @@ def extract_messages_and_tools(conn) -> None:
266
266
  tool_results_by_id[str(span_tool_call_id)]['result_json'] = _to_json_text(span_tool_result_raw)
267
267
  input_messages = _normalize_messages(input_raw, 'user')
268
268
  output_messages = _normalize_messages(output_raw, 'assistant')
269
- for (i, msg) in enumerate(input_messages):
269
+ for i, msg in enumerate(input_messages):
270
270
  content = msg.get('content', msg.get('parts', msg.get('text', '')))
271
271
  if isinstance(content, list):
272
- for (j, block) in enumerate(content):
272
+ for j, block in enumerate(content):
273
273
  if not isinstance(block, dict):
274
274
  continue
275
275
  block_type = str(block.get('type', '')).lower()
@@ -279,7 +279,7 @@ def extract_messages_and_tools(conn) -> None:
279
279
  if not call_id:
280
280
  continue
281
281
  tool_results_by_id[str(call_id)] = {'result_json': _to_json_text(block.get('content', block.get('result'))), 'result_text': _extract_text_content(block.get('content', block.get('result'))), 'status': 'ok', 'updated_at_ms': int(start_ms) + j if start_ms is not None else 0}
282
- for (i, msg) in enumerate(output_messages):
282
+ for i, msg in enumerate(output_messages):
283
283
  content = msg.get('content', msg.get('parts', msg.get('text', '')))
284
284
  output_ts = int(end_ms) if end_ms is not None else int(start_ms) if start_ms is not None else 0
285
285
  tool_blocks: list[dict[str, Any]] = []
@@ -297,7 +297,7 @@ def extract_messages_and_tools(conn) -> None:
297
297
  tool_blocks.append({'type': 'function_call', 'function': function_call})
298
298
  if not tool_blocks:
299
299
  continue
300
- for (j, block) in enumerate(tool_blocks):
300
+ for j, block in enumerate(tool_blocks):
301
301
  if not isinstance(block, dict):
302
302
  continue
303
303
  block_type = str(block.get('type', '')).lower()
@@ -357,7 +357,7 @@ def ingest_single_jsonl_file(conn, jsonl_file: Path) -> None:
357
357
  conn.execute('\n UPDATE turns AS t\n SET turn_index = ranked.turn_index\n FROM (\n SELECT\n turn_id,\n row_number() OVER (PARTITION BY session_id ORDER BY started_at, turn_id) - 1 AS turn_index\n FROM turns\n WHERE session_id IN (SELECT session_id FROM tmp_touched_sessions)\n ) ranked\n WHERE t.turn_id = ranked.turn_id\n ')
358
358
 
359
359
  def upsert_processed_file(conn, jsonl_file: Path) -> None:
360
- (mtime_ms, size_bytes) = get_file_stats(jsonl_file)
360
+ mtime_ms, size_bytes = get_file_stats(jsonl_file)
361
361
  conn.execute('\n INSERT INTO processed_files (file_path, mtime_ms, size_bytes, processed_at)\n VALUES (?, ?, ?, now())\n ON CONFLICT(file_path) DO UPDATE SET\n mtime_ms = excluded.mtime_ms,\n size_bytes = excluded.size_bytes,\n processed_at = excluded.processed_at\n ', [str(jsonl_file), mtime_ms, size_bytes])
362
362
 
363
363
  def main() -> int:
@@ -424,7 +424,7 @@ def main() -> int:
424
424
  log.debug(f'Ingestion complete. Total model_calls: {total_records}')
425
425
  conn.close()
426
426
  log.info(f'Indexing complete: {db_path}')
427
- print(json.dumps({'status': 'ok', 'indexed': True, 'message': f"Indexed {len(changed_files)} changed file(s) at {state['indexedAt']}"}))
427
+ print(json.dumps({'status': 'ok', 'indexed': True, 'message': f'Indexed {len(changed_files)} changed file(s) at {state['indexedAt']}'}))
428
428
  return 0
429
429
  except Exception as e:
430
430
  log.error(f'Indexing failed: {e}')
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agenthippo-ai/agenthippo",
3
- "version": "1.110.34",
3
+ "version": "1.110.35",
4
4
  "description": "AgentHippo CLI — serve, chat, and deploy agent packs",
5
5
  "author": "AgentHippo <dev@agenthippo.ai>",
6
6
  "license": "SEE LICENSE IN LICENSE",