swarm_sdk 2.7.7 → 2.7.9

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: b83fd113813e0667b02d12c5ef6b9f4d47c134afa0665e2b6e482a6c9d3dacd7
4
- data.tar.gz: 8a758e06817af7690c21fc446597b0e1a91b876643d20a4a111a62bc953d3d5e
3
+ metadata.gz: d6af3c1d2b4f71135747902e3d94b4541904bf1877c02064cb928cccc742cf9b
4
+ data.tar.gz: a829be669e85d25c1fa85cc05410b44bb3d9785ad0a72bfe238ae92f1859c828
5
5
  SHA512:
6
- metadata.gz: 5bfcfaf82ca105e6b1e1f04e009c703b641c25af6797ccd72c945a0c8dbbfbe60a0d783fc9e8d43322bbefac95b16123c4e7232c4ac9e1850a770d1120568344
7
- data.tar.gz: 0aa16f8d6ebe0fd2acb4cb52baba570000c82c4607d015d3a4569d671f9743cb1199edd80953002b8dc0fb673fb464ebfc2d5d0c645f3afb50258c7451281304
6
+ metadata.gz: af53d4fa1e8a4ae2ace5297e90743ddd219e3015f3ffa62e89dedb607f73e9641d3cb3d44b506a76e2a70a72074aa3f504229cb4a7d02b056af6b0b555c59bdd
7
+ data.tar.gz: 333359fe2c10fbb772675ae035fa00de4eb1a1cf9d3f5ee7e7b4440d8d6bd25cfc96d1efbadf8621e5bf3c6ef53f8e790a8d31db3c682c1ac7f47d61507c4843
@@ -52,23 +52,23 @@ module SwarmSDK
52
52
  # Execute request
53
53
  @app.call(env).on_complete do |response_env|
54
54
  end_time = Time.now
55
- duration = end_time - start_time
55
+
56
+ # Determine if this was a streaming request based on whether chunks were accumulated
57
+ # This is more reliable than parsing response content
58
+ is_streaming = accumulated_raw_chunks.any?
56
59
 
57
60
  # For streaming: use accumulated raw SSE chunks
58
61
  # For non-streaming: use response body
59
- raw_body = if accumulated_raw_chunks.any?
60
- accumulated_raw_chunks.join
61
- else
62
- response_env.body
63
- end
62
+ raw_body = is_streaming ? accumulated_raw_chunks.join : response_env.body
64
63
 
65
64
  # Store SSE body in Fiber-local for citation extraction
66
65
  # This allows append_citations_to_content to access the full SSE body
67
66
  # even though response.body is empty for streaming responses
68
- Fiber[:last_sse_body] = raw_body if accumulated_raw_chunks.any?
67
+ Fiber[:last_sse_body] = raw_body if is_streaming
69
68
 
70
69
  # Emit response event
71
- emit_response_event(response_env, start_time, end_time, duration, raw_body)
70
+ timing = { start_time: start_time, end_time: end_time, duration: end_time - start_time }
71
+ emit_response_event(response_env, timing, raw_body, is_streaming)
72
72
  end
73
73
  end
74
74
 
@@ -96,21 +96,17 @@ module SwarmSDK
96
96
  # Emit response event
97
97
  #
98
98
  # @param env [Faraday::Env] Response environment
99
- # @param start_time [Time] Request start time
100
- # @param end_time [Time] Request end time
101
- # @param duration [Float] Request duration in seconds
99
+ # @param timing [Hash] Timing information with :start_time, :end_time, :duration keys
102
100
  # @param raw_body [String, nil] Raw response body (SSE stream for streaming, JSON for non-streaming)
101
+ # @param streaming [Boolean] Whether this was a streaming response (determined by chunk accumulation)
103
102
  # @return [void]
104
- def emit_response_event(env, start_time, end_time, duration, raw_body)
105
- # Detect if this is a streaming response (starts with "data:")
106
- streaming = raw_body.is_a?(String) && raw_body.start_with?("data:")
107
-
103
+ def emit_response_event(env, timing, raw_body, streaming)
108
104
  response_data = {
109
105
  provider: @provider_name,
110
106
  body: parse_body(raw_body),
111
107
  streaming: streaming,
112
- duration_seconds: duration.round(3),
113
- timestamp: end_time.utc.iso8601,
108
+ duration_seconds: timing[:duration].round(3),
109
+ timestamp: timing[:end_time].utc.iso8601,
114
110
  status: env.status,
115
111
  }
116
112
 
@@ -166,6 +162,9 @@ module SwarmSDK
166
162
 
167
163
  # Parse request/response body
168
164
  #
165
+ # For requests: returns parsed JSON hash
166
+ # For responses: returns full body (JSON parsed or raw string for SSE)
167
+ #
169
168
  # @param body [String, Hash, nil] HTTP body
170
169
  # @return [Hash, String, nil] Parsed body
171
170
  def parse_body(body)
@@ -177,8 +176,9 @@ module SwarmSDK
177
176
  # Try to parse JSON
178
177
  JSON.parse(body)
179
178
  rescue JSON::ParserError
180
- # Return truncated string if not JSON
181
- body.to_s[0..1000]
179
+ # Return full body for SSE/non-JSON responses
180
+ # Don't truncate - let consumers decide how to handle large bodies
181
+ body.to_s
182
182
  rescue StandardError
183
183
  nil
184
184
  end
@@ -92,6 +92,7 @@ module SwarmSDK
92
92
  mcp_log_level: ["SWARM_SDK_MCP_LOG_LEVEL", -> { Defaults::Logging::MCP_LOG_LEVEL }],
93
93
  default_execution_timeout: ["SWARM_SDK_DEFAULT_EXECUTION_TIMEOUT", -> { Defaults::Timeouts::EXECUTION_TIMEOUT_SECONDS }],
94
94
  default_turn_timeout: ["SWARM_SDK_DEFAULT_TURN_TIMEOUT", -> { Defaults::Timeouts::TURN_TIMEOUT_SECONDS }],
95
+ mcp_request_timeout: ["SWARM_SDK_MCP_REQUEST_TIMEOUT", -> { Defaults::Timeouts::MCP_REQUEST_SECONDS }],
95
96
  }.freeze
96
97
 
97
98
  # WebFetch and control settings
@@ -94,6 +94,47 @@ module SwarmSDK
94
94
  # Time-to-live for cached response IDs. 5 minutes allows conversation
95
95
  # continuity while preventing stale cache issues.
96
96
  RESPONSES_API_TTL_SECONDS = 300
97
+
98
+ # MCP client request timeout (seconds)
99
+ #
100
+ # Default timeout for MCP server connections. 5 minutes accommodates
101
+ # long-running SSE streams and tool executions. This timeout applies to
102
+ # the entire operation (operation_timeout in HTTPX), so it must be long
103
+ # enough for SSE connections that may run for extended periods.
104
+ MCP_REQUEST_SECONDS = 300
105
+ end
106
+
107
+ # MCP reconnection configuration
108
+ #
109
+ # Settings for automatic reconnection when SSE/streamable connections drop.
110
+ # Note: The background SSE notification stream uses operation_timeout which
111
+ # limits total connection duration. Since this stream is meant to stay open
112
+ # indefinitely for server notifications, we configure aggressive reconnection
113
+ # so timeouts are transparent to users. Tool calls use separate connections
114
+ # and are unaffected by SSE stream timeouts.
115
+ module McpReconnection
116
+ # Maximum number of reconnection attempts
117
+ #
118
+ # Very high value (effectively infinite) because the SSE notification stream
119
+ # is expected to timeout periodically due to operation_timeout limitations.
120
+ # Reconnection is transparent - tool calls continue working regardless.
121
+ MAX_RETRIES = 1000
122
+
123
+ # Initial delay between reconnection attempts (milliseconds)
124
+ #
125
+ # Fast initial reconnect (500ms) to minimize notification gaps.
126
+ INITIAL_DELAY_MS = 500
127
+
128
+ # Exponential backoff growth factor
129
+ #
130
+ # Slow growth (1.2x) because we expect frequent reconnections.
131
+ # 500ms -> 600ms -> 720ms -> 864ms -> 1037ms -> ...
132
+ DELAY_GROW_FACTOR = 1.2
133
+
134
+ # Maximum delay between reconnection attempts (milliseconds)
135
+ #
136
+ # Caps at 10 seconds to ensure responsive reconnection even after many retries.
137
+ MAX_DELAY_MS = 10_000
97
138
  end
98
139
 
99
140
  # Output and content size limits