datagrout-conduit 0.5.0 → 0.7.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 +4 -4
- data/lib/datagrout_conduit/client.rb +11 -3
- data/lib/datagrout_conduit/transport/ws.rb +56 -1
- data/lib/datagrout_conduit/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 177af76b0cdd7d7e82fd1384db89da6a5dd4b3152ff5f07e255075dec041a91c
|
|
4
|
+
data.tar.gz: 34b1c4d17471013349db85beb09f167719e4fc0189204b117042c1394fc175f8
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 6812db873347c88a86bfd0ecce380e6d264b4f66854d3eeb01e3ee4a28b89cf2b6e8f93423442ea9b0c14fcb40c68cd6bcc2d6210a5f343200115dd92343ff35
|
|
7
|
+
data.tar.gz: 23deaeee4cfe9be72cc546b919e5a87cb5ea5fa108831c346a4e02b0faa501578b582632f538a42dd0456095a9b8589511e349318c95f414b086bed7a0d90980
|
|
@@ -388,16 +388,24 @@ module DatagroutConduit
|
|
|
388
388
|
unwrap_content(raw)
|
|
389
389
|
end
|
|
390
390
|
|
|
391
|
-
# Unwrap the MCP content envelope
|
|
392
|
-
#
|
|
391
|
+
# Unwrap the MCP content envelope from tool call results.
|
|
392
|
+
#
|
|
393
|
+
# Priority order (MCP 2025):
|
|
394
|
+
# 1. +structuredContent+ — pure JSON Hash, no decoding needed.
|
|
395
|
+
# 2. +content[0]["text"]+ parsed as JSON — legacy text-encoded path.
|
|
396
|
+
# 3. +content[0]+ as-is — plain-text or non-JSON content item.
|
|
397
|
+
# 4. +raw+ unchanged — no content envelope present.
|
|
393
398
|
def unwrap_content(raw)
|
|
394
399
|
return raw unless raw.is_a?(Hash)
|
|
395
400
|
|
|
401
|
+
# 1. Prefer structuredContent (MCP 2025).
|
|
402
|
+
return raw["structuredContent"] if raw.key?("structuredContent")
|
|
403
|
+
|
|
396
404
|
content = raw["content"]
|
|
397
405
|
return raw unless content.is_a?(Array) && !content.empty?
|
|
398
406
|
|
|
399
407
|
first = content.first
|
|
400
|
-
return
|
|
408
|
+
return first unless first.is_a?(Hash) && first["text"].is_a?(String)
|
|
401
409
|
|
|
402
410
|
begin
|
|
403
411
|
JSON.parse(first["text"])
|
|
@@ -32,6 +32,14 @@ module DatagroutConduit
|
|
|
32
32
|
class Ws
|
|
33
33
|
SUBPROTOCOL = "datagrout-jsonrpc.v1"
|
|
34
34
|
|
|
35
|
+
# Seconds between client-initiated WS ping frames.
|
|
36
|
+
#
|
|
37
|
+
# Many load balancers and reverse proxies (nginx, AWS ALB) close idle
|
|
38
|
+
# WS connections after 60-120 seconds; pinging every 25 seconds keeps
|
|
39
|
+
# the connection alive well within the tightest common timeout window.
|
|
40
|
+
# Mirrors PING_INTERVAL in the Rust reference implementation.
|
|
41
|
+
PING_INTERVAL_SECONDS = 25
|
|
42
|
+
|
|
35
43
|
# ── Subscription ─────────────────────────────────────────────────────────
|
|
36
44
|
|
|
37
45
|
# Per-subscription event stream delivered via a thread-safe Queue.
|
|
@@ -98,7 +106,7 @@ module DatagroutConduit
|
|
|
98
106
|
|
|
99
107
|
# ── Construction ─────────────────────────────────────────────────────────
|
|
100
108
|
|
|
101
|
-
def initialize(url:, auth: {}, identity: nil)
|
|
109
|
+
def initialize(url:, auth: {}, identity: nil, ping_interval: PING_INTERVAL_SECONDS)
|
|
102
110
|
@url = url
|
|
103
111
|
@auth = normalize_auth(auth)
|
|
104
112
|
@identity = identity
|
|
@@ -113,9 +121,16 @@ module DatagroutConduit
|
|
|
113
121
|
@io = nil
|
|
114
122
|
@driver = nil
|
|
115
123
|
@read_thread = nil
|
|
124
|
+
@ping_thread = nil
|
|
125
|
+
@ping_interval = ping_interval.to_f
|
|
126
|
+
@pings_sent = 0
|
|
116
127
|
@connected = false
|
|
117
128
|
end
|
|
118
129
|
|
|
130
|
+
# Number of ping frames this transport has sent over its lifetime.
|
|
131
|
+
# Exposed for tests; production code can ignore it.
|
|
132
|
+
attr_reader :pings_sent, :ping_interval
|
|
133
|
+
|
|
119
134
|
# ── Public API ────────────────────────────────────────────────────────────
|
|
120
135
|
|
|
121
136
|
# Establish the WebSocket connection.
|
|
@@ -145,6 +160,7 @@ module DatagroutConduit
|
|
|
145
160
|
raise ConnectionError, "WebSocket handshake failed: #{err}" if err
|
|
146
161
|
|
|
147
162
|
@connected = true
|
|
163
|
+
start_ping_thread
|
|
148
164
|
self
|
|
149
165
|
rescue Timeout::Error
|
|
150
166
|
cleanup_socket
|
|
@@ -423,6 +439,7 @@ module DatagroutConduit
|
|
|
423
439
|
end
|
|
424
440
|
|
|
425
441
|
def cleanup_socket
|
|
442
|
+
stop_ping_thread
|
|
426
443
|
@write_mutex.synchronize do
|
|
427
444
|
@driver = nil
|
|
428
445
|
end
|
|
@@ -432,6 +449,44 @@ module DatagroutConduit
|
|
|
432
449
|
@io = nil
|
|
433
450
|
end
|
|
434
451
|
|
|
452
|
+
# ── Ping keepalive ───────────────────────────────────────────────────────
|
|
453
|
+
|
|
454
|
+
# Start the background ping thread. Sends one WS ping frame every
|
|
455
|
+
# @ping_interval seconds until the connection is torn down. No-op when
|
|
456
|
+
# @ping_interval is non-positive (used by tests to disable pinging).
|
|
457
|
+
def start_ping_thread
|
|
458
|
+
return if @ping_thread
|
|
459
|
+
return if @ping_interval <= 0
|
|
460
|
+
|
|
461
|
+
interval = @ping_interval
|
|
462
|
+
@ping_thread = Thread.new do
|
|
463
|
+
loop do
|
|
464
|
+
sleep interval
|
|
465
|
+
break unless @connected
|
|
466
|
+
|
|
467
|
+
driver = @driver
|
|
468
|
+
break unless driver
|
|
469
|
+
|
|
470
|
+
begin
|
|
471
|
+
# WebSocket::Driver#ping returns false if the connection is
|
|
472
|
+
# already closing; treat that as "stop the loop" too.
|
|
473
|
+
@write_mutex.synchronize { driver.ping } || break
|
|
474
|
+
@pings_sent += 1
|
|
475
|
+
rescue StandardError
|
|
476
|
+
break
|
|
477
|
+
end
|
|
478
|
+
end
|
|
479
|
+
end
|
|
480
|
+
@ping_thread.abort_on_exception = false
|
|
481
|
+
@ping_thread.name = "conduit-ws-ping"
|
|
482
|
+
end
|
|
483
|
+
|
|
484
|
+
def stop_ping_thread
|
|
485
|
+
thread = @ping_thread
|
|
486
|
+
@ping_thread = nil
|
|
487
|
+
thread&.kill
|
|
488
|
+
end
|
|
489
|
+
|
|
435
490
|
# ── Helpers ───────────────────────────────────────────────────────────────
|
|
436
491
|
|
|
437
492
|
def ensure_connected!
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: datagrout-conduit
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.7.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- DataGrout
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-05-
|
|
11
|
+
date: 2026-05-26 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: faraday
|