apidepth 0.4.0 → 0.5.0

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 48290c633c238b8bbfcaff2a1f98cb4ffa26a3d0c02853ef0f345841425b463e
4
- data.tar.gz: b3564982b79da91fe2ad976c913aa8bf94efbd35928f78a52d934e43b02eb4d0
3
+ metadata.gz: d59e0f9e95678afd4a0f9ce173de1b63ac893ab01cbc68fc1d6bbcedc44bfaad
4
+ data.tar.gz: 29524e4a1687cafb241caa6c07dbb2c1139ef2e44bc92f8ded25dc15c1305a92
5
5
  SHA512:
6
- metadata.gz: 7a932948137e2d17f75d346686e60f0287c06f2702ec03a7adf3458c521604d9691e7803921726d391f1e161c59f77bce15063a5eeda93800093d7bb7d75e3ac
7
- data.tar.gz: d262bd3e9b0668a1f5c667160efba8a41b99b850fcf177d3ccf3a5398b7179a005d9b141fb1ef62e1e3a86d55533fce7cf7eaf96c71f01a059ba96d98888ff9e
6
+ metadata.gz: a4a88ac5df80e6af2987cfa29d354c67c5275c02e30caacbae086558d2a0ee1fe1304a42c11b679b2ec6a1358a1cd0e69c3969a5d9d400c246723592df448599
7
+ data.tar.gz: 2005a0c7164ed0b20da0e1013cdc250be7d09f66fdfdd65f465168f38f4a105bfa6370e3a98bef015502f7cb2ba961a99189a6232e0098eb47ca11c588d90ed4
data/README.md CHANGED
@@ -56,6 +56,46 @@ Get your API key at [apidepth.io](https://apidepth.io).
56
56
 
57
57
  ---
58
58
 
59
+ ## CLI
60
+
61
+ The gem ships two subcommands for setup and connectivity verification.
62
+
63
+ ### `bundle exec apidepth setup`
64
+
65
+ Interactive wizard that detects your framework (Rails, Sinatra, or generic), generates the correct initializer snippet, and optionally writes it to disk.
66
+
67
+ ```bash
68
+ bundle exec apidepth setup
69
+ ```
70
+
71
+ For CI/CD pipelines, skip all prompts:
72
+
73
+ ```bash
74
+ bundle exec apidepth setup --api-key $APIDEPTH_API_KEY --no-prompt
75
+ ```
76
+
77
+ | Flag | Description |
78
+ |---|---|
79
+ | `--api-key <key>` | Inject your API key into the generated snippet. |
80
+ | `--no-prompt` | Non-interactive mode — print snippet to stdout and exit. |
81
+ | `--framework <name>` | Override auto-detection (`rails`, `sinatra`, `generic`). |
82
+ | `--ignored-hosts <patterns>` | Comma-separated host patterns to add to `ignored_hosts` (glob wildcards supported). |
83
+ | `--collector-url <url>` | Override the collector URL in the generated snippet. |
84
+
85
+ ### `bundle exec apidepth test`
86
+
87
+ Sends a synthetic test event to the collector and confirms the pipeline is working end-to-end. Reads `APIDEPTH_API_KEY` (and optionally `APIDEPTH_COLLECTOR_URL`) from the environment. Prints the round-trip time on success, or a per-failure-mode error message with next steps on failure.
88
+
89
+ ```bash
90
+ bundle exec apidepth test
91
+ # ✓ received in 142ms
92
+ # Visit your dashboard: https://apidepth.io/dashboard
93
+ ```
94
+
95
+ Exits with code 1 on any error (bad key, unreachable, SSL failure, timeout).
96
+
97
+ ---
98
+
59
99
  ## Configuration
60
100
 
61
101
  All options with their defaults:
@@ -18,9 +18,10 @@ module Apidepth
18
18
  :registry_refresh_interval,
19
19
  :registry_cache_path,
20
20
  :on_flush_error,
21
- :environment, # e.g. "production" — set by Railtie from Rails.env
22
- :sample_rate, # Float 0.0–1.0, default 1.0 (100% of events captured)
23
- :extra_vendors # Hash of vendor_name => host, e.g. { "my-api" => "api.myservice.com" }
21
+ :environment, # e.g. "production" — set by Railtie from Rails.env
22
+ :sample_rate, # Float 0.0–1.0, default 1.0 (100% of events captured)
23
+ :extra_vendors, # Hash of vendor_name => host, e.g. { "my-api" => "api.myservice.com" }
24
+ :capture_model_names # Boolean — read model field from AI vendor JSON responses
24
25
 
25
26
  attr_reader :ignored_hosts, :collector_url
26
27
 
@@ -35,6 +36,7 @@ module Apidepth
35
36
  @environment = nil # Railtie sets this to Rails.env at boot
36
37
  @sample_rate = 1.0 # capture everything by default
37
38
  @extra_vendors = {} # customer-defined host mappings
39
+ @capture_model_names = true # read model field from AI vendor JSON responses
38
40
  _rebuild_ignored_hosts
39
41
  end
40
42
 
@@ -0,0 +1,51 @@
1
+ # lib/apidepth/model_name_extractor.rb
2
+ require "json"
3
+ require "set"
4
+ #
5
+ # Extracts the model name from AI vendor JSON response bodies.
6
+ #
7
+ # WHY response body rather than headers?
8
+ # AI vendors (OpenAI, Anthropic, Gemini, Mistral, Cohere) return the active
9
+ # model in the response body ({"model":"claude-3-opus-20240229",...}), not in
10
+ # headers. This is the only reliable source.
11
+ #
12
+ # WHY only for known AI vendor hosts?
13
+ # Body reads add a tiny overhead. Scoping to a hard-coded allowlist keeps the
14
+ # hot path for non-AI vendors completely unaffected.
15
+ #
16
+ # Body safety: Net::HTTP::HTTPResponse#body memoizes after the first read.
17
+ # Calling it here and returning the response to the application is safe — the
18
+ # application receives the same cached body bytes.
19
+ #
20
+ # Streaming safety: streamed responses have Content-Type: text/event-stream, not
21
+ # application/json. The content-type guard exits early before any body read.
22
+ # The 8KB truncation is a belt-and-suspenders guard against unusually large bodies.
23
+
24
+ module Apidepth
25
+ module ModelNameExtractor
26
+ AI_VENDOR_HOSTS = %w[
27
+ api.openai.com
28
+ api.anthropic.com
29
+ generativelanguage.googleapis.com
30
+ api.mistral.ai
31
+ api.cohere.com
32
+ ].to_set.freeze
33
+
34
+ MAX_BODY_BYTES = 8_192
35
+
36
+ def self.extract(host, response)
37
+ return nil unless Apidepth.configuration.capture_model_names
38
+ return nil unless AI_VENDOR_HOSTS.include?(host)
39
+ return nil unless response["content-type"]&.include?("application/json")
40
+
41
+ body = response.body
42
+ return nil if body.nil? || body.empty?
43
+
44
+ parsed = JSON.parse(body.byteslice(0, MAX_BODY_BYTES), symbolize_names: true)
45
+ model = parsed[:model]
46
+ model.is_a?(String) && !model.empty? ? model : nil
47
+ rescue JSON::ParserError, Encoding::UndefinedConversionError, TypeError
48
+ nil
49
+ end
50
+ end
51
+ end
@@ -72,21 +72,23 @@ module Apidepth
72
72
 
73
73
  now_ms = Process.clock_gettime(Process::CLOCK_REALTIME, :millisecond)
74
74
  rl = Apidepth::RateLimitHeaders.extract(response, now_ms)
75
+ model_name = Apidepth::ModelNameExtractor.extract(address, response)
76
+
77
+ event_attrs = {
78
+ vendor: vendor,
79
+ endpoint: normalized_path,
80
+ method: req.method,
81
+ status: status,
82
+ outcome: outcome,
83
+ duration_ms: duration_ms,
84
+ cold_start: cold_start,
85
+ env: resolve_env,
86
+ ts: now_ms
87
+ }.merge(rl || {})
88
+ event_attrs[:model_name] = model_name if model_name
75
89
 
76
90
  Apidepth::Collector.instance.record(
77
- Apidepth::Event.build(
78
- {
79
- vendor: vendor,
80
- endpoint: normalized_path,
81
- method: req.method,
82
- status: status,
83
- outcome: outcome,
84
- duration_ms: duration_ms,
85
- cold_start: cold_start,
86
- env: resolve_env,
87
- ts: now_ms
88
- }.merge(rl || {})
89
- )
91
+ Apidepth::Event.build(event_attrs)
90
92
  )
91
93
  rescue StandardError => e
92
94
  Apidepth.logger&.debug("[Apidepth] Instrumentation error: #{e.class}: #{e.message}")
@@ -1,5 +1,5 @@
1
1
  # lib/apidepth/version.rb
2
2
 
3
3
  module Apidepth
4
- VERSION = "0.4.0".freeze
4
+ VERSION = "0.5.0".freeze
5
5
  end
data/lib/apidepth.rb CHANGED
@@ -1,14 +1,15 @@
1
1
  # lib/apidepth.rb
2
2
  #
3
3
  # Main entry point. Require order matters:
4
- # 1. version — no dependencies
5
- # 2. configuration — no dependencies
6
- # 3. vendor_registry — no dependencies, boots from BUNDLED_BASELINE immediately
7
- # 4. rate_limit_headers — no dependencies; used by net_http_instrumentation
8
- # 5. net_http_instrumentationdepends on vendor_registry + collector (via lazy reference)
9
- # 5. collector — depends on configuration
10
- # 6. registry_loader — depends on collector + vendor_registry
11
- # 7. railtie — depends on all of the above; only loaded in a Rails context
4
+ # 1. version — no dependencies
5
+ # 2. configuration — no dependencies
6
+ # 3. vendor_registry — no dependencies, boots from BUNDLED_BASELINE immediately
7
+ # 4. rate_limit_headers — no dependencies; used by net_http_instrumentation
8
+ # 5. model_name_extractorno dependencies; used by net_http_instrumentation
9
+ # 6. net_http_instrumentation — depends on vendor_registry + collector (via lazy reference)
10
+ # 7. collector — depends on configuration
11
+ # 8. registry_loader — depends on collector + vendor_registry
12
+ # 9. railtie — depends on all of the above; only loaded in a Rails context
12
13
 
13
14
  require "logger"
14
15
  require "apidepth/version"
@@ -16,6 +17,7 @@ require "apidepth/configuration"
16
17
  require "apidepth/event"
17
18
  require "apidepth/vendor_registry"
18
19
  require "apidepth/rate_limit_headers"
20
+ require "apidepth/model_name_extractor"
19
21
  require "apidepth/net_http_instrumentation"
20
22
  require "apidepth/collector"
21
23
  require "apidepth/registry_loader"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: apidepth
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.0
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Apidepth
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-05-30 00:00:00.000000000 Z
11
+ date: 2026-06-03 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: json
@@ -129,6 +129,7 @@ files:
129
129
  - lib/apidepth/collector.rb
130
130
  - lib/apidepth/configuration.rb
131
131
  - lib/apidepth/event.rb
132
+ - lib/apidepth/model_name_extractor.rb
132
133
  - lib/apidepth/net_http_instrumentation.rb
133
134
  - lib/apidepth/railtie.rb
134
135
  - lib/apidepth/rate_limit_headers.rb