golf-mcp 0.1.19__py3-none-any.whl → 0.2.0__py3-none-any.whl

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.

Potentially problematic release.


This version of golf-mcp might be problematic. Click here for more details.

Files changed (123) hide show
  1. golf/__init__.py +9 -1
  2. golf/_endpoints.py +6 -0
  3. golf/_endpoints_fallback.py +10 -0
  4. golf/auth/__init__.py +188 -84
  5. golf/auth/api_key.py +6 -14
  6. golf/auth/factory.py +333 -0
  7. golf/auth/helpers.py +12 -42
  8. golf/auth/providers.py +396 -0
  9. golf/auth/registry.py +256 -0
  10. golf/cli/branding.py +192 -0
  11. golf/cli/main.py +28 -69
  12. golf/commands/__init__.py +2 -0
  13. golf/commands/build.py +4 -7
  14. golf/commands/init.py +30 -53
  15. golf/commands/run.py +50 -20
  16. golf/core/builder.py +356 -412
  17. golf/core/builder_auth.py +63 -144
  18. golf/core/builder_telemetry.py +26 -3
  19. golf/core/config.py +38 -59
  20. golf/core/parser.py +132 -139
  21. golf/core/platform.py +12 -10
  22. golf/core/telemetry.py +11 -19
  23. golf/core/transformer.py +38 -15
  24. golf/examples/__pycache__/__init__.cpython-311.pyc +0 -0
  25. golf/examples/basic/.coverage +0 -0
  26. golf/examples/basic/.env.example +8 -4
  27. golf/examples/basic/README.md +117 -45
  28. golf/examples/basic/__pycache__/auth.cpython-311.pyc +0 -0
  29. golf/examples/basic/auth.py +76 -0
  30. golf/examples/basic/golf.json +2 -5
  31. golf/examples/basic/htmlcov/.gitignore +2 -0
  32. golf/examples/basic/htmlcov/class_index.html +547 -0
  33. golf/examples/basic/htmlcov/coverage_html_cb_6fb7b396.js +733 -0
  34. golf/examples/basic/htmlcov/favicon_32_cb_58284776.png +0 -0
  35. golf/examples/basic/htmlcov/function_index.html +2091 -0
  36. golf/examples/basic/htmlcov/index.html +349 -0
  37. golf/examples/basic/htmlcov/keybd_closed_cb_ce680311.png +0 -0
  38. golf/examples/basic/htmlcov/status.json +1 -0
  39. golf/examples/basic/htmlcov/style_cb_8e611ae1.css +337 -0
  40. golf/examples/basic/htmlcov/z_1c9a91c0e91c8496___init___py.html +323 -0
  41. golf/examples/basic/htmlcov/z_1c9a91c0e91c8496_api_key_py.html +170 -0
  42. golf/examples/basic/htmlcov/z_1c9a91c0e91c8496_factory_py.html +430 -0
  43. golf/examples/basic/htmlcov/z_1c9a91c0e91c8496_helpers_py.html +288 -0
  44. golf/examples/basic/htmlcov/z_1c9a91c0e91c8496_providers_py.html +493 -0
  45. golf/examples/basic/htmlcov/z_1c9a91c0e91c8496_registry_py.html +353 -0
  46. golf/examples/basic/htmlcov/z_3ec3b3f490dc0950___init___py.html +120 -0
  47. golf/examples/basic/htmlcov/z_3ec3b3f490dc0950_instrumentation_py.html +1535 -0
  48. golf/examples/basic/htmlcov/z_4b8b9dd4ccccc5db___init___py.html +98 -0
  49. golf/examples/basic/htmlcov/z_4b8b9dd4ccccc5db_branding_py.html +289 -0
  50. golf/examples/basic/htmlcov/z_4b8b9dd4ccccc5db_main_py.html +476 -0
  51. golf/examples/basic/htmlcov/z_5a6c4e6bcc86fb2f___init___py.html +97 -0
  52. golf/examples/basic/htmlcov/z_6cadab9ec0df475d___init___py.html +102 -0
  53. golf/examples/basic/htmlcov/z_6cadab9ec0df475d_build_py.html +178 -0
  54. golf/examples/basic/htmlcov/z_6cadab9ec0df475d_init_py.html +387 -0
  55. golf/examples/basic/htmlcov/z_6cadab9ec0df475d_run_py.html +222 -0
  56. golf/examples/basic/htmlcov/z_6fcdee0582ba84e4___init___py.html +106 -0
  57. golf/examples/basic/htmlcov/z_6fcdee0582ba84e4__endpoints_fallback_py.html +107 -0
  58. golf/examples/basic/htmlcov/z_7ba499ed22986217___init___py.html +98 -0
  59. golf/examples/basic/htmlcov/z_7ba499ed22986217_builder_auth_py.html +306 -0
  60. golf/examples/basic/htmlcov/z_7ba499ed22986217_builder_metrics_py.html +329 -0
  61. golf/examples/basic/htmlcov/z_7ba499ed22986217_builder_py.html +1471 -0
  62. golf/examples/basic/htmlcov/z_7ba499ed22986217_builder_telemetry_py.html +186 -0
  63. golf/examples/basic/htmlcov/z_7ba499ed22986217_config_py.html +315 -0
  64. golf/examples/basic/htmlcov/z_7ba499ed22986217_parser_py.html +1149 -0
  65. golf/examples/basic/htmlcov/z_7ba499ed22986217_platform_py.html +279 -0
  66. golf/examples/basic/htmlcov/z_7ba499ed22986217_telemetry_py.html +589 -0
  67. golf/examples/basic/htmlcov/z_7ba499ed22986217_transformer_py.html +286 -0
  68. golf/examples/basic/htmlcov/z_7d7da37693a43688___init___py.html +107 -0
  69. golf/examples/basic/htmlcov/z_7d7da37693a43688_collector_py.html +417 -0
  70. golf/examples/basic/htmlcov/z_7d7da37693a43688_registry_py.html +109 -0
  71. golf/examples/basic/htmlcov/z_abe733142b40ad4e___init___py.html +109 -0
  72. golf/examples/basic/htmlcov/z_abe733142b40ad4e_context_py.html +150 -0
  73. golf/examples/basic/htmlcov/z_abe733142b40ad4e_elicitation_py.html +267 -0
  74. golf/examples/basic/htmlcov/z_abe733142b40ad4e_sampling_py.html +318 -0
  75. golf/examples/basic/prompts/__pycache__/welcome.cpython-311.pyc +0 -0
  76. golf/examples/basic/prompts/welcome.py +3 -5
  77. golf/examples/basic/resources/__pycache__/current_time.cpython-311.pyc +0 -0
  78. golf/examples/basic/resources/__pycache__/info.cpython-311.pyc +0 -0
  79. golf/examples/basic/resources/current_time.py +5 -13
  80. golf/examples/basic/resources/weather/__pycache__/common.cpython-311.pyc +0 -0
  81. golf/examples/basic/resources/weather/__pycache__/current.cpython-311.pyc +0 -0
  82. golf/examples/basic/resources/weather/__pycache__/forecast.cpython-311.pyc +0 -0
  83. golf/examples/basic/resources/weather/city.py +46 -0
  84. golf/examples/basic/resources/weather/common.py +4 -11
  85. golf/examples/basic/resources/weather/current.py +5 -5
  86. golf/examples/basic/resources/weather/forecast.py +5 -5
  87. golf/examples/basic/tools/__pycache__/calculator.cpython-311.pyc +0 -0
  88. golf/examples/basic/tools/calculator.py +94 -0
  89. golf/examples/basic/tools/say/__pycache__/hello.cpython-311.pyc +0 -0
  90. golf/examples/basic/tools/say/hello.py +65 -0
  91. golf/metrics/collector.py +100 -19
  92. golf/telemetry/__init__.py +4 -0
  93. golf/telemetry/instrumentation.py +496 -174
  94. golf/utilities/__init__.py +12 -0
  95. golf/utilities/context.py +53 -0
  96. golf/utilities/elicitation.py +170 -0
  97. golf/utilities/sampling.py +221 -0
  98. {golf_mcp-0.1.19.dist-info → golf_mcp-0.2.0.dist-info}/METADATA +56 -110
  99. golf_mcp-0.2.0.dist-info/RECORD +110 -0
  100. golf/auth/oauth.py +0 -861
  101. golf/auth/provider.py +0 -115
  102. golf/examples/api_key/.env +0 -2
  103. golf/examples/api_key/.env.example +0 -1
  104. golf/examples/api_key/README.md +0 -84
  105. golf/examples/api_key/golf.json +0 -8
  106. golf/examples/api_key/pre_build.py +0 -11
  107. golf/examples/api_key/tools/issues/create.py +0 -93
  108. golf/examples/api_key/tools/issues/list.py +0 -92
  109. golf/examples/api_key/tools/repos/list.py +0 -111
  110. golf/examples/api_key/tools/search/code.py +0 -106
  111. golf/examples/api_key/tools/users/get.py +0 -82
  112. golf/examples/basic/.env +0 -5
  113. golf/examples/basic/pre_build.py +0 -28
  114. golf/examples/basic/tools/github_user.py +0 -65
  115. golf/examples/basic/tools/hello.py +0 -34
  116. golf/examples/basic/tools/payments/charge.py +0 -70
  117. golf/examples/basic/tools/payments/common.py +0 -36
  118. golf/examples/basic/tools/payments/refund.py +0 -61
  119. golf_mcp-0.1.19.dist-info/RECORD +0 -60
  120. {golf_mcp-0.1.19.dist-info → golf_mcp-0.2.0.dist-info}/WHEEL +0 -0
  121. {golf_mcp-0.1.19.dist-info → golf_mcp-0.2.0.dist-info}/entry_points.txt +0 -0
  122. {golf_mcp-0.1.19.dist-info → golf_mcp-0.2.0.dist-info}/licenses/LICENSE +0 -0
  123. {golf_mcp-0.1.19.dist-info → golf_mcp-0.2.0.dist-info}/top_level.txt +0 -0
@@ -5,6 +5,7 @@ sharing functionality across multiple resources in a directory.
5
5
  """
6
6
 
7
7
  import os
8
+ from typing import Any
8
9
 
9
10
  # Read configuration from environment variables
10
11
  WEATHER_API_KEY = os.environ.get("WEATHER_API_KEY", "mock_key")
@@ -15,30 +16,22 @@ TEMPERATURE_UNIT = os.environ.get("WEATHER_TEMP_UNIT", "fahrenheit")
15
16
  class WeatherApiClient:
16
17
  """Mock weather API client."""
17
18
 
18
- def __init__(
19
- self, api_key: str = WEATHER_API_KEY, api_url: str = WEATHER_API_URL
20
- ) -> None:
19
+ def __init__(self, api_key: str = WEATHER_API_KEY, api_url: str = WEATHER_API_URL) -> None:
21
20
  self.api_key = api_key
22
21
  self.api_url = api_url
23
22
  self.unit = TEMPERATURE_UNIT
24
23
 
25
- async def get_forecast(self, city: str, days: int = 3):
24
+ async def get_forecast(self, city: str, days: int = 3) -> dict[str, Any]:
26
25
  """Get weather forecast for a city (mock implementation)."""
27
26
  # This would make an API call in a real implementation
28
- print(
29
- f"Would call {self.api_url}/forecast/{city} with API key {self.api_key[:4]}..."
30
- )
31
27
  return {
32
28
  "city": city,
33
29
  "unit": self.unit,
34
30
  "forecast": [{"day": i, "temp": 70 + i} for i in range(days)],
35
31
  }
36
32
 
37
- async def get_current(self, city: str):
33
+ async def get_current(self, city: str) -> dict[str, Any]:
38
34
  """Get current weather for a city (mock implementation)."""
39
- print(
40
- f"Would call {self.api_url}/current/{city} with API key {self.api_key[:4]}..."
41
- )
42
35
  return {
43
36
  "city": city,
44
37
  "unit": self.unit,
@@ -6,19 +6,19 @@ from typing import Any
6
6
  from .common import weather_client
7
7
 
8
8
  # The URI that clients will use to access this resource
9
- resource_uri = "weather://current/{city}"
9
+ resource_uri = "weather://current"
10
10
 
11
11
 
12
- async def current_weather(city: str) -> dict[str, Any]:
13
- """Provide current weather for the specified city.
12
+ async def current_weather() -> dict[str, Any]:
13
+ """Provide current weather for a default city.
14
14
 
15
15
  This example demonstrates:
16
16
  1. Nested resource organization (resources/weather/current.py)
17
- 2. Dynamic URI parameters (city in this case)
17
+ 2. Resource without URI parameters
18
18
  3. Using shared client from the common.py file
19
19
  """
20
20
  # Use the shared weather client from common.py
21
- weather_data = await weather_client.get_current(city)
21
+ weather_data = await weather_client.get_current("New York")
22
22
 
23
23
  # Add some additional data
24
24
  weather_data.update(
@@ -6,19 +6,19 @@ from typing import Any
6
6
  from .common import weather_client
7
7
 
8
8
  # The URI that clients will use to access this resource
9
- resource_uri = "weather://forecast/{city}"
9
+ resource_uri = "weather://forecast"
10
10
 
11
11
 
12
- async def forecast_weather(city: str) -> dict[str, Any]:
13
- """Provide a weather forecast for the specified city.
12
+ async def forecast_weather() -> dict[str, Any]:
13
+ """Provide a weather forecast for a default city.
14
14
 
15
15
  This example demonstrates:
16
16
  1. Nested resource organization (resources/weather/forecast.py)
17
- 2. Dynamic URI parameters (city in this case)
17
+ 2. Resource without URI parameters
18
18
  3. Using shared client from the common.py file
19
19
  """
20
20
  # Use the shared weather client from common.py
21
- forecast_data = await weather_client.get_forecast(city, days=5)
21
+ forecast_data = await weather_client.get_forecast("New York", days=5)
22
22
 
23
23
  # Add some additional data
24
24
  forecast_data.update(
@@ -0,0 +1,94 @@
1
+ """Enhanced calculator tool with optional LLM-powered explanations."""
2
+
3
+ from typing import Annotated
4
+
5
+ from pydantic import BaseModel, Field
6
+ from golf.utilities import sample
7
+
8
+
9
+ class CalculationResult(BaseModel):
10
+ """Result of a mathematical calculation."""
11
+
12
+ result: float
13
+ operation: str
14
+ expression: str
15
+
16
+
17
+ async def calculate(
18
+ expression: Annotated[
19
+ str,
20
+ Field(
21
+ description="Mathematical expression to evaluate (e.g., '2 + 3', '10 * 5', '100 / 4')",
22
+ examples=["2 + 3", "10 * 5.5", "(8 - 3) * 2"],
23
+ ),
24
+ ],
25
+ explain: Annotated[
26
+ bool,
27
+ Field(
28
+ description="Whether to provide an LLM-powered step-by-step explanation",
29
+ default=False,
30
+ ),
31
+ ] = False,
32
+ ) -> CalculationResult:
33
+ """Evaluate a mathematical expression with optional LLM explanation.
34
+
35
+ This enhanced calculator can:
36
+ - Perform basic arithmetic operations (+, -, *, /, parentheses)
37
+ - Handle decimal numbers
38
+ - Optionally provide LLM-powered step-by-step explanations
39
+
40
+ Examples:
41
+ - calculate("2 + 3") → 5
42
+ - calculate("10 * 5.5") → 55.0
43
+ - calculate("(8 - 3) * 2", explain=True) → 10 with explanation
44
+ """
45
+ try:
46
+ # Simple expression evaluation using eval (safe for basic math)
47
+ # In production, consider using a proper math expression parser
48
+ allowed_chars = set("0123456789+-*/.() ")
49
+ if not all(c in allowed_chars for c in expression):
50
+ raise ValueError("Expression contains invalid characters")
51
+
52
+ # Evaluate the expression
53
+ result = eval(expression, {"__builtins__": {}}, {})
54
+
55
+ # Ensure result is a number
56
+ if not isinstance(result, (int, float)):
57
+ raise ValueError("Expression did not evaluate to a number")
58
+
59
+ # Generate explanation if requested
60
+ result_expression = expression
61
+ if explain:
62
+ try:
63
+ explanation = await sample(
64
+ f"Explain this mathematical expression step by step: {expression} = {result}",
65
+ system_prompt="You are a helpful math tutor. Provide clear, step-by-step explanations.",
66
+ max_tokens=200,
67
+ )
68
+ result_expression = f"{expression}\n\nExplanation: {explanation}"
69
+ except Exception:
70
+ # If sampling fails, continue without explanation
71
+ result_expression = f"{expression}\n\n(Explanation unavailable)"
72
+
73
+ return CalculationResult(
74
+ result=float(result),
75
+ operation="evaluate",
76
+ expression=result_expression,
77
+ )
78
+
79
+ except ZeroDivisionError:
80
+ return CalculationResult(
81
+ result=float("inf"),
82
+ operation="error",
83
+ expression=f"{expression} → Division by zero",
84
+ )
85
+ except Exception as e:
86
+ return CalculationResult(
87
+ result=0.0,
88
+ operation="error",
89
+ expression=f"{expression} → Error: {str(e)}",
90
+ )
91
+
92
+
93
+ # Export the tool
94
+ export = calculate
@@ -0,0 +1,65 @@
1
+ """Enhanced hello tool with elicitation capabilities."""
2
+
3
+ from typing import Annotated
4
+
5
+ from pydantic import BaseModel, Field
6
+ from golf.utilities import elicit
7
+
8
+
9
+ class Output(BaseModel):
10
+ """Response from the hello tool."""
11
+
12
+ message: str
13
+
14
+
15
+ async def hello(
16
+ name: Annotated[str, Field(description="The name of the person to greet")] = "World",
17
+ greeting: Annotated[str, Field(description="The greeting phrase to use")] = "Hello",
18
+ personalized: Annotated[
19
+ bool,
20
+ Field(
21
+ description="Whether to ask for additional personal details to create a personalized greeting",
22
+ default=False,
23
+ ),
24
+ ] = False,
25
+ ) -> Output:
26
+ """Say hello with optional personalized elicitation.
27
+
28
+ This enhanced tool can:
29
+ - Provide basic greetings
30
+ - Elicit additional personal information for personalized messages
31
+ - Demonstrate Golf's elicitation capabilities
32
+
33
+ Examples:
34
+ - hello("Alice") → "Hello, Alice!"
35
+ - hello("Bob", personalized=True) → Asks for details, then personalized greeting
36
+ """
37
+ # Basic greeting
38
+ basic_message = f"{greeting}, {name}!"
39
+
40
+ # If personalized greeting is requested, elicit additional info
41
+ if personalized:
42
+ try:
43
+ # Ask for user's mood
44
+ mood = await elicit(
45
+ "How are you feeling today?",
46
+ ["happy", "excited", "calm", "focused", "creative"],
47
+ )
48
+
49
+ # Create personalized message
50
+ personalized_message = f"{greeting}, {name}! Hope you're having a {mood} day!"
51
+
52
+ return Output(message=personalized_message)
53
+
54
+ except Exception as e:
55
+ # If elicitation fails, fall back to basic greeting
56
+ print(f"Personalization failed: {e}")
57
+ return Output(message=f"{basic_message} (personalization unavailable)")
58
+
59
+ # Return basic greeting
60
+ print(f"{greeting} {name}...")
61
+ return Output(message=basic_message)
62
+
63
+
64
+ # Designate the entry point function
65
+ export = hello
golf/metrics/collector.py CHANGED
@@ -66,6 +66,38 @@ class MetricsCollector:
66
66
  ["prompt_name"],
67
67
  )
68
68
 
69
+ # Sampling metrics
70
+ self._metrics["sampling_requests"] = Counter(
71
+ "golf_sampling_requests_total",
72
+ "Total number of sampling requests",
73
+ ["sampling_type", "status"],
74
+ )
75
+
76
+ self._metrics["sampling_duration"] = Histogram(
77
+ "golf_sampling_duration_seconds",
78
+ "Sampling request duration in seconds",
79
+ ["sampling_type"],
80
+ )
81
+
82
+ self._metrics["sampling_tokens"] = Histogram(
83
+ "golf_sampling_tokens",
84
+ "Number of tokens in sampling responses",
85
+ ["sampling_type"],
86
+ )
87
+
88
+ # Elicitation metrics
89
+ self._metrics["elicitation_requests"] = Counter(
90
+ "golf_elicitation_requests_total",
91
+ "Total number of elicitation requests",
92
+ ["elicitation_type", "status"],
93
+ )
94
+
95
+ self._metrics["elicitation_duration"] = Histogram(
96
+ "golf_elicitation_duration_seconds",
97
+ "Elicitation request duration in seconds",
98
+ ["elicitation_type"],
99
+ )
100
+
69
101
  # Error metrics
70
102
  self._metrics["errors"] = Counter(
71
103
  "golf_errors_total",
@@ -74,18 +106,14 @@ class MetricsCollector:
74
106
  )
75
107
 
76
108
  # Session metrics
77
- self._metrics["sessions_total"] = Counter(
78
- "golf_sessions_total", "Total number of sessions created"
79
- )
109
+ self._metrics["sessions_total"] = Counter("golf_sessions_total", "Total number of sessions created")
80
110
 
81
111
  self._metrics["session_duration"] = Histogram(
82
112
  "golf_session_duration_seconds", "Session duration in seconds"
83
113
  )
84
114
 
85
115
  # System metrics
86
- self._metrics["uptime"] = Gauge(
87
- "golf_uptime_seconds", "Server uptime in seconds"
88
- )
116
+ self._metrics["uptime"] = Gauge("golf_uptime_seconds", "Server uptime in seconds")
89
117
 
90
118
  except ImportError:
91
119
  # Prometheus client not available, disable metrics
@@ -101,9 +129,7 @@ class MetricsCollector:
101
129
  if not self.enabled or "tool_executions" not in self._metrics:
102
130
  return
103
131
 
104
- self._metrics["tool_executions"].labels(
105
- tool_name=tool_name, status=status
106
- ).inc()
132
+ self._metrics["tool_executions"].labels(tool_name=tool_name, status=status).inc()
107
133
 
108
134
  def record_tool_duration(self, tool_name: str, duration: float) -> None:
109
135
  """Record tool execution duration.
@@ -128,9 +154,7 @@ class MetricsCollector:
128
154
  if not self.enabled or "http_requests" not in self._metrics:
129
155
  return
130
156
 
131
- self._metrics["http_requests"].labels(
132
- method=method, status_code=str(status_code), path=path
133
- ).inc()
157
+ self._metrics["http_requests"].labels(method=method, status_code=str(status_code), path=path).inc()
134
158
 
135
159
  def record_http_duration(self, method: str, path: str, duration: float) -> None:
136
160
  """Record HTTP request duration.
@@ -143,9 +167,7 @@ class MetricsCollector:
143
167
  if not self.enabled or "http_duration" not in self._metrics:
144
168
  return
145
169
 
146
- self._metrics["http_duration"].labels(method=method, path=path).observe(
147
- duration
148
- )
170
+ self._metrics["http_duration"].labels(method=method, path=path).observe(duration)
149
171
 
150
172
  def increment_resource_read(self, resource_uri: str) -> None:
151
173
  """Record a resource read.
@@ -174,14 +196,13 @@ class MetricsCollector:
174
196
 
175
197
  Args:
176
198
  component_type: Type of component ('tool', 'resource', 'prompt', 'http')
177
- error_type: Type of error ('timeout', 'auth_error', 'validation_error', etc.)
199
+ error_type: Type of error ('timeout', 'auth_error',
200
+ 'validation_error', etc.)
178
201
  """
179
202
  if not self.enabled or "errors" not in self._metrics:
180
203
  return
181
204
 
182
- self._metrics["errors"].labels(
183
- component_type=component_type, error_type=error_type
184
- ).inc()
205
+ self._metrics["errors"].labels(component_type=component_type, error_type=error_type).inc()
185
206
 
186
207
  def increment_session(self) -> None:
187
208
  """Record a new session."""
@@ -212,6 +233,66 @@ class MetricsCollector:
212
233
 
213
234
  self._metrics["uptime"].set(seconds)
214
235
 
236
+ def increment_sampling(self, sampling_type: str, status: str) -> None:
237
+ """Record a sampling request.
238
+
239
+ Args:
240
+ sampling_type: Type of sampling ('sample', 'structured', 'context')
241
+ status: Request status ('success' or 'error')
242
+ """
243
+ if not self.enabled or "sampling_requests" not in self._metrics:
244
+ return
245
+
246
+ self._metrics["sampling_requests"].labels(sampling_type=sampling_type, status=status).inc()
247
+
248
+ def record_sampling_duration(self, sampling_type: str, duration: float) -> None:
249
+ """Record sampling request duration.
250
+
251
+ Args:
252
+ sampling_type: Type of sampling
253
+ duration: Request duration in seconds
254
+ """
255
+ if not self.enabled or "sampling_duration" not in self._metrics:
256
+ return
257
+
258
+ self._metrics["sampling_duration"].labels(sampling_type=sampling_type).observe(duration)
259
+
260
+ def record_sampling_tokens(self, sampling_type: str, token_count: int) -> None:
261
+ """Record sampling token count.
262
+
263
+ Args:
264
+ sampling_type: Type of sampling
265
+ token_count: Number of tokens in the response
266
+ """
267
+ if not self.enabled or "sampling_tokens" not in self._metrics:
268
+ return
269
+
270
+ self._metrics["sampling_tokens"].labels(sampling_type=sampling_type).observe(token_count)
271
+
272
+ def increment_elicitation(self, elicitation_type: str, status: str) -> None:
273
+ """Record an elicitation request.
274
+
275
+ Args:
276
+ elicitation_type: Type of elicitation ('elicit', 'confirmation')
277
+ status: Request status ('success' or 'error')
278
+ """
279
+ if not self.enabled or "elicitation_requests" not in self._metrics:
280
+ return
281
+
282
+ self._metrics["elicitation_requests"].labels(elicitation_type=elicitation_type, status=status).inc()
283
+
284
+ def record_elicitation_duration(self, elicitation_type: str, duration: float) -> None:
285
+ """Record elicitation request duration.
286
+
287
+ Args:
288
+ elicitation_type: Type of elicitation
289
+ duration: Request duration in seconds
290
+ """
291
+ if not self.enabled or "elicitation_duration" not in self._metrics:
292
+ return
293
+
294
+ self._metrics["elicitation_duration"].labels(elicitation_type=elicitation_type).observe(duration)
295
+
215
296
 
216
297
  def init_metrics_collector(enabled: bool = False) -> MetricsCollector:
217
298
  """Initialize the global metrics collector.
@@ -3,8 +3,10 @@
3
3
  from golf.telemetry.instrumentation import (
4
4
  get_tracer,
5
5
  init_telemetry,
6
+ instrument_elicitation,
6
7
  instrument_prompt,
7
8
  instrument_resource,
9
+ instrument_sampling,
8
10
  instrument_tool,
9
11
  telemetry_lifespan,
10
12
  )
@@ -13,6 +15,8 @@ __all__ = [
13
15
  "instrument_tool",
14
16
  "instrument_resource",
15
17
  "instrument_prompt",
18
+ "instrument_elicitation",
19
+ "instrument_sampling",
16
20
  "telemetry_lifespan",
17
21
  "init_telemetry",
18
22
  "get_tracer",