@agenthippo-ai/agenthippo 0.2.3-dataplane → 1.110.11

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 (139) hide show
  1. package/out/agentpack/index.js +3 -1
  2. package/out/agentpack/paths.js +25 -2
  3. package/out/analytics/dashboard/localFleetAdapter.js +82 -2
  4. package/out/analytics/dashboard/provider.js +13 -2
  5. package/out/analytics/dashboard/webview/panel.js +3 -2
  6. package/out/analytics/identity.js +0 -0
  7. package/out/analytics/service.js +29 -25
  8. package/out/analytics/sessionEvents.js +100 -24
  9. package/out/auth/authBaseUrl.js +59 -0
  10. package/out/auth/bootstrapApply.js +9 -10
  11. package/out/auth/brokerClient.js +48 -14
  12. package/out/auth/provider.js +8 -21
  13. package/out/auth/sessionStore.js +3 -40
  14. package/out/auth/signInFlow.js +2 -1
  15. package/out/auth-broker/config.js +50 -5
  16. package/out/auth-broker/server.js +126 -26
  17. package/out/auth-broker/store.js +25 -24
  18. package/out/authority/warrant.js +206 -0
  19. package/out/branding.js +27 -1
  20. package/out/cli/commands/conformance.js +249 -0
  21. package/out/cli/commands/data-plane.js +151 -0
  22. package/out/cli/commands/deploy.js +1 -0
  23. package/out/cli/commands/gateway.js +4 -0
  24. package/out/cli/commands/pack.js +182 -3
  25. package/out/cli/commands/sandbox.js +351 -0
  26. package/out/cli/commands/serve.js +1145 -130
  27. package/out/cli/commands/store.js +12 -4
  28. package/out/cli/credential-context.js +17 -0
  29. package/out/cli/data-plane/gateway.js +28 -5
  30. package/out/cli/data-plane/index.js +18 -2
  31. package/out/cli/data-plane/registry.js +9 -0
  32. package/out/cli/data-plane/remote.js +106 -0
  33. package/out/cli/data-plane/sidecar.js +225 -0
  34. package/out/cli/deploy/layers/identity.js +88 -4
  35. package/out/cli/deploy-init.js +89 -24
  36. package/out/cli/deps.js +106 -2
  37. package/out/cli/idp-init.js +35 -0
  38. package/out/cli/index.js +171 -17
  39. package/out/cli/pack-install-ref.js +15 -0
  40. package/out/cli/pack-loader.js +8 -1
  41. package/out/cli/pack-runtime.js +61 -0
  42. package/out/cli/runtime.js +35 -1
  43. package/out/cli/serve-hardening.js +260 -14
  44. package/out/cli/utils.js +12 -1
  45. package/out/engines/base.js +32 -3
  46. package/out/engines/claude.js +33 -15
  47. package/out/engines/codex.js +111 -27
  48. package/out/engines/custom.js +15 -1
  49. package/out/evidence/sessionShip.js +643 -0
  50. package/out/extension.js +11 -2
  51. package/out/gateway/bridge.js +40 -2
  52. package/out/gateway/client.js +34 -1
  53. package/out/gateway/installSource.js +68 -0
  54. package/out/gateway/lifecycle.js +185 -51
  55. package/out/gateway/outboundPaths.js +4 -1
  56. package/out/gateway/secretsPreflight.js +90 -0
  57. package/out/gateway/service.js +7 -2
  58. package/out/headless/index.js +25 -2
  59. package/out/live-e2e/mcp-demo/server.mjs +32 -0
  60. package/out/mcp.js +30 -0
  61. package/out/pack/caps.js +271 -0
  62. package/out/pack/governor.js +326 -0
  63. package/out/pack/lease-schema.js +273 -0
  64. package/out/pack/lease.js +682 -0
  65. package/out/pack/reconcile.js +217 -0
  66. package/out/pack/signature.js +839 -0
  67. package/out/remote/handleRemoteChat.js +44 -3
  68. package/out/remote/remoteTrust.js +277 -0
  69. package/out/remote/serveClient.js +52 -5
  70. package/out/runner/host.js +141 -0
  71. package/out/runner/protocol.js +128 -0
  72. package/out/runner/remoteEngine.js +166 -0
  73. package/out/runtime/interfaces/edit-tracker.js +2 -0
  74. package/out/runtime/vscode/vscode-edit-tracker.js +2 -0
  75. package/out/sandbox/containerDriver.js +572 -0
  76. package/out/sandbox/processDriver.js +318 -0
  77. package/out/sandbox/sandboxRunner.js +303 -0
  78. package/out/sandbox/support.js +301 -0
  79. package/out/sandbox/types.js +55 -0
  80. package/out/scripts/agentide-litellm.sh +67 -5
  81. package/out/scripts/agentide_litellm_hooks_v1.py +266 -21
  82. package/out/scripts/analytics/agentide-litellm-otel.config.yaml +98 -132
  83. package/out/scripts/analytics/setup-analytics.ps1 +45 -14
  84. package/out/scripts/analytics/setup-analytics.sh +106 -15
  85. package/out/scripts/analytics/start-analytics.sh +86 -14
  86. package/out/scripts/gateway/setup-gateway.sh +72 -32
  87. package/out/shared/models.js +11 -110
  88. package/out/spotlight/install.js +7 -1
  89. package/out/store/index.js +41 -10
  90. package/out/sync.js +3 -1
  91. package/out/test/analytics.test.js +162 -23
  92. package/out/test/auth-baseurl.test.js +132 -0
  93. package/out/test/auth-bootstrapApply.test.js +27 -6
  94. package/out/test/auth-broker-hardening.test.js +227 -0
  95. package/out/test/auth-broker.test.js +59 -9
  96. package/out/test/branding.test.js +13 -0
  97. package/out/test/caps-governor.test.js +460 -0
  98. package/out/test/codex-new-file-survival.test.js +119 -0
  99. package/out/test/configuration-scopes.test.js +131 -0
  100. package/out/test/data-plane-engine-wiring.test.js +32 -0
  101. package/out/test/data-plane.test.js +380 -0
  102. package/out/test/deploy-init.test.js +47 -1
  103. package/out/test/gateway-client.test.js +28 -0
  104. package/out/test/gateway-install-source.test.js +111 -0
  105. package/out/test/gateway-lifecycle.test.js +1 -1
  106. package/out/test/gateway-outbound.test.js +7 -0
  107. package/out/test/gateway-secrets-preflight.test.js +127 -0
  108. package/out/test/gateway-session-routing.test.js +18 -0
  109. package/out/test/governor-hardening.test.js +222 -0
  110. package/out/test/headless-sync.test.js +22 -0
  111. package/out/test/lease-signature.test.js +357 -0
  112. package/out/test/lease-trust-root.test.js +273 -0
  113. package/out/test/lease-validation.test.js +224 -0
  114. package/out/test/lease.test.js +114 -0
  115. package/out/test/localFleetAdapter.test.js +91 -0
  116. package/out/test/mcp.test.js +27 -1
  117. package/out/test/pack-admission.test.js +216 -0
  118. package/out/test/pack-digest-parity.test.js +140 -0
  119. package/out/test/pack-reconcile.test.js +183 -0
  120. package/out/test/pack-signature-artifact.test.js +103 -0
  121. package/out/test/pack-signature.test.js +176 -0
  122. package/out/test/pack-trust-root.test.js +202 -0
  123. package/out/test/remote-trust.test.js +242 -0
  124. package/out/test/runner-protocol.test.js +249 -0
  125. package/out/test/sandbox-driver.test.js +553 -0
  126. package/out/test/sandbox-hardening.test.js +406 -0
  127. package/out/test/sandbox-runner.test.js +343 -0
  128. package/out/test/sandbox-scope.test.js +449 -0
  129. package/out/test/serve-cron-acceptance.test.js +370 -0
  130. package/out/test/serve-gateway-turn.test.js +122 -0
  131. package/out/test/serve-hardening.test.js +227 -15
  132. package/out/test/serveClient.test.js +6 -5
  133. package/out/test/sessionShip.test.js +618 -0
  134. package/out/test/spotlightPublish.test.js +28 -0
  135. package/out/test/store-install-safe.test.js +69 -0
  136. package/out/test/tls-bypass.test.js +90 -0
  137. package/out/test/warrant.test.js +138 -0
  138. package/out/utils.js +46 -9
  139. package/package.json +1 -1
@@ -1,5 +1,6 @@
1
1
  #---------------------------------------------------------------------------------------------
2
2
  # Copyright (c) AgentHippo.ai. All rights reserved.
3
+ # Licensed under the MIT License. See LICENSE.txt in the project root for license information.
3
4
  #---------------------------------------------------------------------------------------------
4
5
  """
5
6
  AgentIDE LiteLLM proxy hooks - Clean structured assistant output with separate input/output
@@ -7,11 +8,28 @@ Mimics langfuse_otel style: gen_ai.input.messages (history) + gen_ai.output.mess
7
8
 
8
9
  Usage in litellm config.yaml:
9
10
  callbacks: ["agentide_litellm_hooks_v1.clean_separated_otel_output"]
11
+
12
+ PRIVACY DEFAULT (PLAN/compliance-req.md §6 "data minimization"):
13
+ Prompt and completion CONTENT is NOT captured unless an operator opts in with
14
+
15
+ AGENTHIPPO_EVIDENCE_CONTENT_CAPTURE=1
16
+
17
+ Without the opt-in every span carries metadata only — model, tokens, latency, status — plus a
18
+ sha256 + byte length of the content so a later disclosure can still be *matched* against a
19
+ span without the span holding the text. This is enforced here at span-write time, in addition
20
+ to `turn_off_message_logging: true` in the config, so a config that re-enables message logging
21
+ without the env opt-in still produces redacted spans (defence in depth: the config is edited by
22
+ hand far more often than this file).
10
23
  """
11
24
 
12
25
  from __future__ import annotations
13
26
  from typing import Any
27
+ import ast
28
+ import hashlib
14
29
  import json
30
+ import os
31
+ import re
32
+ import sys
15
33
 
16
34
  from litellm.integrations.custom_logger import CustomLogger
17
35
  from litellm.integrations.opentelemetry import OpenTelemetry
@@ -20,6 +38,192 @@ from opentelemetry.trace import Status, StatusCode
20
38
  _OTEL_TEXT_MAX_LEN = 4000 # per text block
21
39
  _OTEL_PREVIEW_LEN = 180 # short preview in event
22
40
 
41
+ # -----------------------------------------------------------------------------
42
+ # Content capture opt-in (fix: full capture used to be the default)
43
+ # -----------------------------------------------------------------------------
44
+
45
+ CONTENT_CAPTURE_ENV = "AGENTHIPPO_EVIDENCE_CONTENT_CAPTURE"
46
+
47
+ _TRUTHY = frozenset({"1", "true", "yes", "on"})
48
+
49
+ # Span attributes that can carry prompt/completion text. Mirrors CONTENT_ATTRS in
50
+ # @agenthippo-ai/evidence-ship so the two redaction points cannot drift silently.
51
+ _CONTENT_ATTR_RE = re.compile(
52
+ r"^(gen_ai\.(input|output)\.messages"
53
+ r"|gen_ai\.(prompt|completion)"
54
+ r"|gen_ai\.system_instructions"
55
+ r"|llm\.(prompts|responses)"
56
+ r"|llm\.request\.functions\."
57
+ r"|error\.(message|stack_trace)"
58
+ r"|hidden_params"
59
+ r"|metadata\.requester_custom_headers)"
60
+ )
61
+
62
+ _capture_banner_printed = False
63
+
64
+
65
+ def _content_capture_enabled() -> bool:
66
+ """True only when an operator explicitly opted in to prompt/completion capture."""
67
+ return (os.environ.get(CONTENT_CAPTURE_ENV, "") or "").strip().lower() in _TRUTHY
68
+
69
+
70
+ def _announce_content_capture() -> None:
71
+ """Log loudly, once, whichever mode is in force — silence is how this got shipped on."""
72
+ global _capture_banner_printed
73
+ if _capture_banner_printed:
74
+ return
75
+ _capture_banner_printed = True
76
+ if _content_capture_enabled():
77
+ print(
78
+ "\n"
79
+ "!! AGENTHIPPO EVIDENCE: FULL PROMPT/COMPLETION CAPTURE IS ON !!\n"
80
+ f"!! {CONTENT_CAPTURE_ENV} is set. Every span will carry prompt and completion text.\n"
81
+ "!! Unset it to return to metadata-only (sha256 + length) spans.\n",
82
+ file=sys.stderr,
83
+ flush=True,
84
+ )
85
+ else:
86
+ print(
87
+ f"[agentide-otel] content capture OFF (metadata + sha256 only). "
88
+ f"Set {CONTENT_CAPTURE_ENV}=1 to record prompts/completions.",
89
+ file=sys.stderr,
90
+ flush=True,
91
+ )
92
+
93
+
94
+ def _sha256(text: str) -> str:
95
+ return hashlib.sha256(text.encode("utf-8", "replace")).hexdigest()
96
+
97
+
98
+ # Correlation IDs Spotlight reads from the requester header blob (see FLEET_DATA_CONTRACT.md).
99
+ # These are minted by AgentHippo clients and carry no user content, so with capture off the
100
+ # blob is reduced to exactly these keys rather than dropped — unknown x-* headers from clients
101
+ # we do not control (e.g. x-api-key) still never reach the span in plaintext.
102
+ _METADATA_HEADERS_KEY = "metadata.requester_custom_headers"
103
+
104
+ _METADATA_HEADER_ALLOWLIST = frozenset({
105
+ "x-litellm-metadata-session-id",
106
+ "x-litellm-metadata-request-id",
107
+ "x-litellm-metadata-user-id",
108
+ "x-litellm-metadata-project-id",
109
+ "x-litellm-metadata-agent-id",
110
+ "x-litellm-metadata-agent-version",
111
+ "x-litellm-metadata-source",
112
+ "x-litellm-metadata-engine",
113
+ })
114
+
115
+
116
+ def _parse_attr_dict(value: Any) -> Any:
117
+ if not isinstance(value, str):
118
+ return value
119
+ try:
120
+ return ast.literal_eval(value)
121
+ except (ValueError, SyntaxError):
122
+ try:
123
+ return json.loads(value)
124
+ except ValueError:
125
+ return None
126
+
127
+
128
+ def _allowlisted_headers(headers: dict) -> dict:
129
+ return {
130
+ k.lower(): v for k, v in headers.items()
131
+ if isinstance(k, str) and k.lower() in _METADATA_HEADER_ALLOWLIST
132
+ }
133
+
134
+
135
+ def _filtered_metadata_headers(value: Any) -> str | None:
136
+ """Reduce the header blob to allowlisted keys, keeping the str(dict) repr format that
137
+ the DuckDB extraction regexes parse. None when nothing allowlisted survives."""
138
+ headers = _parse_attr_dict(value)
139
+ if not isinstance(headers, dict):
140
+ return None
141
+ kept = _allowlisted_headers(headers)
142
+ return str(kept) if kept else None
143
+
144
+
145
+ # LiteLLM also embeds the raw request headers as metadata.requester_metadata['headers'] on some
146
+ # routes (observed on /v1/chat/completions; absent on /v1/messages). The attribute's scalar keys
147
+ # (session_id, request_id, …) are a fallback landing spot the extraction chain parses, so the
148
+ # attribute itself must survive — only its embedded header dict gets the allowlist treatment.
149
+ _REQUESTER_METADATA_KEY = "metadata.requester_metadata"
150
+
151
+
152
+ def _scrub_requester_metadata(value: Any) -> Any:
153
+ """Scrubbed value, or None (caller drops the attribute) when an embedded header dict
154
+ cannot be safely isolated — reprs with non-literal values defeat ast.literal_eval, and
155
+ passing those through unparsed would leak the very headers this exists to remove."""
156
+ md = _parse_attr_dict(value)
157
+ if isinstance(md, dict) and isinstance(md.get("headers"), dict):
158
+ return str({**md, "headers": _allowlisted_headers(md["headers"])})
159
+ if isinstance(md, dict):
160
+ return value
161
+ return None if "headers" in str(value) else value
162
+
163
+
164
+ class _RedactingSpan:
165
+ """
166
+ Span proxy that drops content-bearing attributes and replaces them with a sha256 + length.
167
+
168
+ LiteLLM's own `set_attributes()` writes `gen_ai.input.messages` (the full history) and
169
+ friends. We cannot remove an attribute after the SDK has recorded it, so the only place to
170
+ stop it is between LiteLLM and the SDK — here. Everything that is not content passes through
171
+ untouched, so the span keeps model, tokens, latency, status, and the requester metadata the
172
+ evidence index reads.
173
+ """
174
+
175
+ def __init__(self, span: Any) -> None:
176
+ self._span = span
177
+
178
+ def __getattr__(self, name: str) -> Any: # everything we do not override
179
+ return getattr(self._span, name)
180
+
181
+ def set_attribute(self, key: Any, value: Any) -> Any:
182
+ if key == _REQUESTER_METADATA_KEY:
183
+ scrubbed = _scrub_requester_metadata(value)
184
+ if scrubbed is None:
185
+ return None
186
+ return self._span.set_attribute(key, scrubbed)
187
+ if isinstance(key, str) and _CONTENT_ATTR_RE.match(key):
188
+ text = value if isinstance(value, str) else json.dumps(value, ensure_ascii=False, default=str)
189
+ self._span.set_attribute(f"{key}.sha256", _sha256(text))
190
+ self._span.set_attribute(f"{key}.length", len(text))
191
+ self._span.set_attribute(f"{key}.redacted", True)
192
+ if key == _METADATA_HEADERS_KEY:
193
+ kept = _filtered_metadata_headers(value)
194
+ if kept is not None:
195
+ self._span.set_attribute(key, kept)
196
+ return None
197
+ return self._span.set_attribute(key, value)
198
+
199
+ def set_attributes(self, attributes: Any) -> Any:
200
+ if isinstance(attributes, dict):
201
+ for key, value in attributes.items():
202
+ self.set_attribute(key, value)
203
+ return None
204
+ return self._span.set_attributes(attributes)
205
+
206
+ def add_event(self, name: Any, attributes: Any = None, **kwargs: Any) -> Any:
207
+ if isinstance(attributes, dict):
208
+ filtered = {}
209
+ for key, value in attributes.items():
210
+ if key == _REQUESTER_METADATA_KEY:
211
+ scrubbed = _scrub_requester_metadata(value)
212
+ if scrubbed is not None:
213
+ filtered[key] = scrubbed
214
+ elif isinstance(key, str) and _CONTENT_ATTR_RE.match(key):
215
+ text = value if isinstance(value, str) else json.dumps(value, ensure_ascii=False, default=str)
216
+ filtered[f"{key}.sha256"] = _sha256(text)
217
+ filtered[f"{key}.length"] = len(text)
218
+ if key == _METADATA_HEADERS_KEY:
219
+ kept = _filtered_metadata_headers(value)
220
+ if kept is not None:
221
+ filtered[key] = kept
222
+ else:
223
+ filtered[key] = value
224
+ attributes = filtered
225
+ return self._span.add_event(name, attributes, **kwargs)
226
+
23
227
 
24
228
  def _read_field(obj: Any, key: str, default: Any = None) -> Any:
25
229
  if isinstance(obj, dict):
@@ -103,9 +307,16 @@ def _is_anthropic_route(model: Any) -> bool:
103
307
  return "claude" in name or "anthropic" in name
104
308
 
105
309
 
310
+ # Every model known to reject the Codex built-in web-search tools. Additive by policy: a
311
+ # deployment that still routes to an older model keeps its sanitizer when a newer one is added,
312
+ # because a dropped entry fails as upstream 400s on live traffic, not as a test failure.
313
+ _WEB_SEARCH_UNSUPPORTED_MODELS = ("gpt-5.6-luna", "gpt-4.1-nano")
314
+
315
+
106
316
  def _is_web_search_unsupported_model(model: Any) -> bool:
107
317
  """True when the upstream model rejects Codex web-search tools."""
108
- return "gpt-4.1-nano" in _model_name(model)
318
+ name = _model_name(model)
319
+ return any(m in name for m in _WEB_SEARCH_UNSUPPORTED_MODELS)
109
320
 
110
321
 
111
322
  # OpenAI Responses API fields that Anthropic (and some gateways) reject if forwarded verbatim.
@@ -360,7 +571,7 @@ def sanitize_messages(messages: Any) -> Any:
360
571
 
361
572
 
362
573
  class ReasoningParamsSanitizer(CustomLogger):
363
- """LiteLLM proxy hook: strip reasoning controls for gpt-4.1-nano."""
574
+ """LiteLLM proxy hook: strip reasoning controls for gpt-5.6-luna."""
364
575
 
365
576
  async def async_pre_call_hook(
366
577
  self,
@@ -410,7 +621,7 @@ class CodexProxySanitizer(CustomLogger):
410
621
  LiteLLM proxy hook: strip Codex/OpenAI Responses fields/tools that break proxy routes.
411
622
 
412
623
  - Anthropic: drop client_metadata (OpenAI-only Responses telemetry).
413
- - gpt-4.1-nano: drop web_search_preview and related built-in search tools.
624
+ - gpt-5.6-luna: drop web_search_preview and related built-in search tools.
414
625
  """
415
626
 
416
627
  def _sanitize(self, payload: dict, model: Any) -> dict:
@@ -548,8 +759,36 @@ class CleanSeparatedOtelOutput(OpenTelemetry):
548
759
  - gen_ai.input.messages → full history (as set by LiteLLM)
549
760
  - gen_ai.output.messages → only the new assistant reply (stringified structured array)
550
761
  - Short preview event for quick visibility
762
+
763
+ Content (both directions) is recorded ONLY when AGENTHIPPO_EVIDENCE_CONTENT_CAPTURE is set.
764
+ Otherwise every content attribute becomes `<key>.sha256` + `<key>.length` — enough to prove
765
+ which bytes a turn saw, without shipping the bytes.
551
766
  """
552
767
 
768
+ def set_attributes(self, span: Any, *args: Any, **kwargs: Any) -> Any:
769
+ """
770
+ Redaction chokepoint. Overriding here rather than at the call site covers every path in
771
+ the base class that sets attributes, including ones this subclass does not override.
772
+ """
773
+ _announce_content_capture()
774
+ if not _content_capture_enabled():
775
+ span = _RedactingSpan(span)
776
+ return super().set_attributes(span, *args, **kwargs)
777
+
778
+ def _get_span_name(self, kwargs: dict) -> str:
779
+ # Evidence pipeline indexes spans named litellm_request; main-stable semconv uses "chat …".
780
+ #
781
+ # The constant is a PRIVATE litellm symbol and this runs on the span-creation path, so an
782
+ # upstream rename would raise for every span — total evidence loss at layer 1, not a
783
+ # degraded span name. The literal is the same value; falling back to it keeps the pipeline
784
+ # indexing while the import is fixed.
785
+ try:
786
+ from litellm.integrations.opentelemetry import LITELLM_REQUEST_SPAN_NAME
787
+
788
+ return LITELLM_REQUEST_SPAN_NAME
789
+ except Exception: # pragma: no cover - depends on the pinned litellm build
790
+ return "litellm_request"
791
+
553
792
  def _start_primary_span(
554
793
  self,
555
794
  kwargs: dict,
@@ -573,24 +812,30 @@ class CleanSeparatedOtelOutput(OpenTelemetry):
573
812
  _extract_assistant_text(response_obj)
574
813
 
575
814
  if text.strip():
576
- # Structured output (separate from input history)
577
- structured_msgs = _make_output_messages_structured(text)
578
- span.set_attribute(
579
- "gen_ai.output.messages",
580
- json.dumps(structured_msgs, ensure_ascii=False)
581
- )
582
-
583
- # Short preview event - easy to spot, low noise
584
- preview = text[:_OTEL_PREVIEW_LEN] + ("..." if len(text) > _OTEL_PREVIEW_LEN else "")
585
- span.add_event(
586
- name="gen_ai.output.generated",
587
- attributes={
588
- "gen_ai.output.preview": preview,
589
- "gen_ai.output.length": len(text),
590
- "gen_ai.output.truncated": len(text) > _OTEL_TEXT_MAX_LEN,
591
- "gen_ai.response.model": _read_field(kwargs, "model", "unknown")
592
- }
593
- )
815
+ capture = _content_capture_enabled()
816
+ event_attrs: dict[str, Any] = {
817
+ # Always present: proves a reply of exactly these bytes was produced, and lets a
818
+ # disclosed transcript be matched back to the span without storing the text.
819
+ "gen_ai.output.sha256": _sha256(text),
820
+ "gen_ai.output.length": len(text),
821
+ "gen_ai.output.truncated": len(text) > _OTEL_TEXT_MAX_LEN,
822
+ "gen_ai.output.captured": capture,
823
+ "gen_ai.response.model": _read_field(kwargs, "model", "unknown"),
824
+ }
825
+
826
+ if capture:
827
+ # Structured output (separate from input history) — opt-in only.
828
+ structured_msgs = _make_output_messages_structured(text)
829
+ span.set_attribute(
830
+ "gen_ai.output.messages",
831
+ json.dumps(structured_msgs, ensure_ascii=False)
832
+ )
833
+ # Short preview event - easy to spot, low noise
834
+ event_attrs["gen_ai.output.preview"] = (
835
+ text[:_OTEL_PREVIEW_LEN] + ("..." if len(text) > _OTEL_PREVIEW_LEN else "")
836
+ )
837
+
838
+ span.add_event(name="gen_ai.output.generated", attributes=event_attrs)
594
839
 
595
840
  span.end(end_time=self._to_ns(end_time))
596
841
  return span
@@ -1,4 +1,7 @@
1
-
1
+ #---------------------------------------------------------------------------------------------
2
+ # Copyright (c) AgentHippo.ai. All rights reserved.
3
+ # Licensed under the MIT License. See LICENSE.txt in the project root for license information.
4
+ #---------------------------------------------------------------------------------------------
2
5
 
3
6
  # AgentIDE LiteLLM configuration with OpenTelemetry
4
7
  # Use this instead of agentide-litellm.config.yaml for lightweight analytics
@@ -19,6 +22,20 @@
19
22
  # OTEL_SERVICE_NAME=agentide-litellm # Service name in traces
20
23
  # DEBUG_OTEL=true # Enable OTEL debug logging
21
24
 
25
+ #
26
+ # PRIVACY DEFAULT — metadata only. See "Content capture" below.
27
+ # AUTHENTICATION — general_settings.master_key is REQUIRED; the proxy fails to start without it.
28
+ # ROUTES — consumer/subscription-credential routes are NOT in this file. See
29
+ # agentide-litellm-consumer-routes.local-dev-only.config.yaml.
30
+ #
31
+
32
+ general_settings:
33
+ # Without a master key LiteLLM accepts unauthenticated POST /v1/chat/completions from anything
34
+ # that can reach the port — on a serve host that is every colocated container, and the proxy
35
+ # holds provider credentials. No default is supplied on purpose: an unset LITELLM_MASTER_KEY
36
+ # must fail closed at startup rather than quietly open the proxy.
37
+ master_key: os.environ/LITELLM_MASTER_KEY
38
+
22
39
  litellm_settings:
23
40
  telemetry: false # Disables sending data to LiteLLM's remote servers
24
41
  drop_params: true
@@ -36,139 +53,88 @@ litellm_settings:
36
53
  # callbacks: ["agentide_litellm_hooks_v1.structured_genai_otel"] # , "agentide_litellm_hooks.tool_order_sanitizer"]
37
54
  # callbacks: ["agentide_litellm_hooks_v1.structured_genai_otel", "agentide_litellm_hooks_v1.reasoning_params_sanitizer"]
38
55
  callbacks: ["agentide_litellm_hooks_v1.structured_genai_otel", "agentide_litellm_hooks_v1.codex_proxy_sanitizer", "agentide_litellm_hooks_v1.tool_order_sanitizer", "agentide_litellm_hooks_v1.empty_thinking_sanitizer", "agentide_litellm_hooks_v1.reasoning_params_sanitizer"]
39
- turn_off_message_logging: false
56
+
57
+ # ---------------------------------------------------------------------------
58
+ # Content capture — OPT-IN (PLAN/compliance-req.md §6, data minimization)
59
+ # ---------------------------------------------------------------------------
60
+ # Default: prompts and completions are NOT written to spans. Every span still carries model,
61
+ # token counts, latency, status, requester metadata, and a sha256 + byte length of the content,
62
+ # so a turn stays attributable and a disclosed transcript can be matched back to it.
63
+ #
64
+ # This used to be `turn_off_message_logging: false` + `message_logging: true`, i.e. full prompt
65
+ # and completion capture on by default, unredacted for the serve lane.
66
+ #
67
+ # TO RECORD PROMPTS/COMPLETIONS (both steps are required — either alone keeps you redacted):
68
+ # 1. set turn_off_message_logging: false and callback_settings.otel.message_logging: true
69
+ # 2. export AGENTHIPPO_EVIDENCE_CONTENT_CAPTURE=1
70
+ # Step 2 is enforced in agentide_litellm_hooks_v1.py at span-write time and logs loudly when on.
71
+ #
72
+ # LANES: these hardened defaults are what Dockerfile.litellm bakes into the gateway image.
73
+ # setup-analytics.{sh,ps1} flips both flags ON when copying this template to a local
74
+ # ~/.agent-hippo/litellm-otel.yaml — the IDE/Spotlight lane records prompts by default.
75
+ turn_off_message_logging: true
40
76
 
41
77
  callback_settings:
42
78
  otel:
43
- message_logging: true # critical for seeing tool_calls, arguments, responses
44
-
45
- model_list:
46
-
47
- - model_name: litellm/gpt-4.1-nano
48
- litellm_params:
49
- model: openai/gpt-4.1-nano
50
- api_base: https://api.openai.com/v1
51
- drop_params: true
52
- additional_drop_params: ["user", "max_output_tokens", "reasoning.effort", "reasoning_effort"] # Must be set at specific model -- global does not work: Force LiteLLM to remove this specific field before sending the request
53
-
54
- # Add this under (existing) model_list:
55
- # model_list:
56
- # - model_name: ...
57
- - model_name: litellm/claude-opus-4-7
58
- litellm_params:
59
- model: anthropic/claude-opus-4-7
60
- reasoning_effort: "medium"
61
- additional_drop_params: ["user", "max_output_tokens"]
62
- store: false
63
- api_key: os.environ/ANTHROPIC_SETUP_TOKEN
64
- model_info:
65
- upstream_provider: "anthropic-setup-token"
66
-
67
- # Add this under (existing) model_list:
68
- # model_list:
69
- # - model_name: ...
70
- - model_name: litellm/claude-haiku-4-5-20251001
71
- litellm_params:
72
- model: anthropic/claude-haiku-4-5-20251001
73
- top_p: null
74
- drop_params: true
75
- api_base: https://api.anthropic.com
76
-
77
- - model_name: litellm/claude-sonnet-4-6
78
- litellm_params:
79
- model: anthropic/claude-sonnet-4-6
80
- api_base: https://api.anthropic.com
81
-
82
-
83
- # Add this under (existing) model_list:
84
- # model_list:
85
- # - model_name: ...
86
- - model_name: litellm/gpt-5.3-codex
87
- litellm_params:
88
- model: openai/gpt-5.3-codex
89
- store: false
90
- api_key: os.environ/OPENAI_CODEX_API_KEY
91
- api_base: https://chatgpt.com/backend-api/codex
92
- reasoning_effort: "medium"
93
- additional_drop_params: ["user", "max_output_tokens"] # Must be set at specific model -- global does not work: Force LiteLLM to remove this specific field before sending the request
94
-
95
- - model_name: litellm/gpt-5.5
96
- litellm_params:
97
- model: openai/gpt-5.5
98
- store: false
99
- api_key: os.environ/OPENAI_API_KEY
100
- api_base: https://api.openai.com/v1
101
- reasoning_effort: "medium"
102
- additional_drop_params: ["user", "max_output_tokens"] # Must be set at specific model -- global does not work: Force LiteLLM to remove this specific field before sending the request
103
-
104
- # Add this under (existing) model_list:
105
- # model_list:
106
- # - model_name: ...
107
- - model_name: litellm/gpt-5.1-codex-mini
108
- litellm_params:
109
- model: openai/gpt-5.1-codex-mini
110
- reasoning_effort: "medium"
111
- additional_drop_params: ["user", "max_output_tokens", "temperature"]
112
- store: false
113
- api_key: os.environ/OPENAI_CODEX_API_KEY
114
- api_base: https://chatgpt.com/backend-api/codex
115
-
116
- # Add this under (existing) model_list:
117
- # model_list:
118
- # - model_name: ...
119
- - model_name: litellm/claude-opus-4-6-subs
120
- litellm_params:
121
- model: anthropic/claude-opus-4-6
122
- reasoning_effort: "medium"
123
- max_tokens: 64000
124
- additional_drop_params: ["user", "max_output_tokens"]
125
- store: false
126
- api_key: os.environ/ANTHROPIC_SETUP_TOKEN
127
-
128
-
129
- - model_name: litellm/claude-haiku-4-5-subs
130
- litellm_params:
131
- model: anthropic/claude-haiku-4-5
132
- reasoning_effort: "medium"
133
- additional_drop_params: ["user", "max_output_tokens"]
134
- store: false
135
- api_key: os.environ/ANTHROPIC_SETUP_TOKEN
136
-
137
- # Add this under (existing) model_list:
138
- # model_list:
139
- # - model_name: ...
140
- - model_name: litellm/claude-opus-4-7-test
141
- litellm_params:
142
- model: anthropic/claude-opus-4-7
143
- reasoning_effort: "medium"
144
- additional_drop_params: ["user", "max_output_tokens"]
145
- store: false
146
- model_info:
147
- upstream_provider: "anthropic"
79
+ message_logging: false # true + AGENTHIPPO_EVIDENCE_CONTENT_CAPTURE=1 records tool_calls, arguments, responses
80
+
81
+ # ---------------------------------------------------------------------------
82
+ # Model routes
83
+ # ---------------------------------------------------------------------------
84
+ # A fresh install ships with no active routes. Uncomment `model_list:` and the
85
+ # provider blocks you use, then put the matching key in ~/.agent-hippo/.env
86
+ # (e.g. echo 'OPENAI_API_KEY=sk-...' >> ~/.agent-hippo/.env). The launcher
87
+ # warns at startup about any referenced env var that is not set.
88
+ #
89
+ # NOTE: `additional_drop_params` must be set per model — the global setting
90
+ # does not work.
91
+ # More providers: https://docs.litellm.ai/docs/providers
92
+ #
93
+ # Consumer/subscription-credential routes (litellm/gpt-5.3-codex,
94
+ # litellm/claude-opus-4-7, …) are deliberately NOT in this file — see
95
+ # agentide-litellm-consumer-routes.local-dev-only.config.yaml and
96
+ # PLAN/compliance.md §12. `litellm/gpt-5.3-codex` is the default model in
97
+ # several docs/manifests; it has NOT been re-pointed at a platform-API route
98
+ # here, because which platform model id is equivalent is a provider question,
99
+ # not a config one — pick one deliberately rather than inheriting a guess.
148
100
 
149
- # Add this under (existing) model_list:
150
101
  # model_list:
151
- # - model_name: ...
152
- - model_name: litellm/gpt-5.4-nano
153
- litellm_params:
154
- model: openai/gpt-5.4-nano
155
- reasoning_effort: "medium"
156
- additional_drop_params: ["user", "max_output_tokens"]
157
- store: false
158
- api_base: https://api.openai.com/v1
159
- model_info:
160
- upstream_provider: "openai"
161
-
162
- # Fleet / Cloud Run: ah-auto resolves via the central AgentHippo model gateway (same as
163
- # colocated agents with AGENTHIPPO_LITELLM_BASE_URL=https://gateway.agenthippo.ai/v1).
164
- # Set AGENTHIPPO_GATEWAY_KEY to an ahgw_… service key (create-gateway-key on the gateway host).
165
- - model_name: ah-auto
166
- litellm_params:
167
- # openai/ prefix required — bare `ah-auto` fails LiteLLM router deployment (no provider).
168
- model: openai/ah-auto
169
- api_base: https://gateway.agenthippo.ai/v1
170
- api_key: os.environ/AGENTHIPPO_GATEWAY_KEY
171
- drop_params: true
172
- additional_drop_params: ["user", "max_output_tokens", "reasoning.effort", "reasoning_effort"]
173
- model_info:
174
- upstream_provider: "agenthippo-gateway"
102
+ # # --- OpenAI (platform API) ---
103
+ # - model_name: litellm/gpt-5.5
104
+ # litellm_params:
105
+ # model: openai/gpt-5.5
106
+ # api_base: https://api.openai.com/v1
107
+ # api_key: os.environ/OPENAI_API_KEY
108
+ # drop_params: true
109
+ # additional_drop_params: ["user", "max_output_tokens"]
110
+ #
111
+ # # --- Anthropic (platform API) ---
112
+ # - model_name: litellm/claude-sonnet-4-6
113
+ # litellm_params:
114
+ # model: anthropic/claude-sonnet-4-6
115
+ # api_base: https://api.anthropic.com
116
+ # api_key: os.environ/ANTHROPIC_API_KEY
117
+ # drop_params: true
118
+ #
119
+ # # --- AgentHippo model gateway (fleet / Cloud Run) ---
120
+ # # Same endpoint as colocated agents with
121
+ # # AGENTHIPPO_LITELLM_BASE_URL=https://gateway.agenthippo.ai/v1.
122
+ # # AGENTHIPPO_GATEWAY_KEY is an ahgw_… service key (create-gateway-key on
123
+ # # the gateway host).
124
+ # - model_name: ah-auto
125
+ # litellm_params:
126
+ # # openai/ prefix required — bare `ah-auto` fails LiteLLM router
127
+ # # deployment (no provider).
128
+ # model: openai/ah-auto
129
+ # api_base: https://gateway.agenthippo.ai/v1
130
+ # api_key: os.environ/AGENTHIPPO_GATEWAY_KEY
131
+ # drop_params: true
132
+ # additional_drop_params: ["user", "max_output_tokens", "reasoning.effort", "reasoning_effort"]
133
+ #
134
+ # # --- Distributed LiteLLM OTEL: central model gateway ---
135
+ # - model_name: ah-auto
136
+ # litellm_params:
137
+ # model: openai/ah-auto
138
+ # api_base: https://gatewayv1.agenthippo.ai/v1
139
+ # api_key: os.environ/AGENTHIPPO_GATEWAY_KEY
140
+ # drop_params: true