simforge 0.6.0 → 0.8.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: 0eaad388f5419c3933530ac93808928f219682b1e32d35fff040f691cbd4f410
4
- data.tar.gz: a7922a5c2a3f063b5f6c13f43e5f6defed320c82d9b8d887a3cc12293ab03808
3
+ metadata.gz: 73a5e6deba272d1145d6403c0101d8f32a694f236ac4a976d02eb11b79b17c25
4
+ data.tar.gz: 89481b2c5f3e7a00946aba1e438c21df60b9c8138a04720b1a22701a35095c73
5
5
  SHA512:
6
- metadata.gz: 32b1df24c8a1dd4aa8acb6b5ca6828736c1a3b5586982a169865acaaa753075b4e06009f6c2d0d6843e9ec29539cba0a28c4e0df83eaaeef65f31b98e1449637
7
- data.tar.gz: 2abbabfde142faa7c8ca0adc6e7433787f16b6b60aaafd28d26bd23c0fc890be852bdae342312540fc97e081bea05d163af66ae1b043f393c05c1f63ddd94d44
6
+ metadata.gz: 2626a260759ff51764a1b6497b29ac1493acbc86ea9a0e347a39bd5b02987c5bc4012d7b5e7839887f634da72643370c75b6835a92da59c6b7768ae525fae6e2
7
+ data.tar.gz: 45b37048fc7d7338135f5b2d9cd8d6a4a111034587d055aeb9b58e9cd44ac562a1e00e19b9833293f68afc81ff5b47e184e31e410e5c7a795739ef2bdea17e5f
@@ -23,6 +23,8 @@ module Simforge
23
23
  @enabled = false
24
24
  end
25
25
  @http_client = HttpClient.new(api_key:, service_url: @service_url)
26
+ @pending_span_threads = {}
27
+ @pending_span_mutex = Mutex.new
26
28
  end
27
29
 
28
30
  # Execute a block inside a span context, sending trace data on completion.
@@ -42,9 +44,14 @@ module Simforge
42
44
  TraceState.create(trace_id)
43
45
  end
44
46
 
47
+ if is_root_span
48
+ @pending_span_mutex.synchronize { @pending_span_threads[trace_id] = [] }
49
+ end
50
+
45
51
  result = nil
46
52
  error = nil
47
53
  span_contexts = nil
54
+ span_prompt = nil
48
55
 
49
56
  begin
50
57
  SpanContext.with_span(trace_id:, span_id:) do
@@ -52,6 +59,7 @@ module Simforge
52
59
  ensure
53
60
  # Capture contexts before the span context is popped
54
61
  span_contexts = SpanContext.current&.dig(:contexts)
62
+ span_prompt = SpanContext.current&.dig(:prompt)
55
63
  end
56
64
  rescue => e
57
65
  error = e.message
@@ -61,7 +69,7 @@ module Simforge
61
69
  begin
62
70
  ended_at = Time.now.utc.strftime("%Y-%m-%dT%H:%M:%S.%3NZ")
63
71
 
64
- send_span(
72
+ span_thread = send_span(
65
73
  trace_function_key:,
66
74
  trace_id:,
67
75
  span_id:,
@@ -70,6 +78,7 @@ module Simforge
70
78
  span_type:,
71
79
  function_name:,
72
80
  contexts: span_contexts,
81
+ prompt: span_prompt,
73
82
  args:,
74
83
  kwargs:,
75
84
  result:,
@@ -78,14 +87,21 @@ module Simforge
78
87
  ended_at:
79
88
  )
80
89
 
81
- # For root spans, also send trace completion
82
90
  if is_root_span
91
+ pending = @pending_span_mutex.synchronize { @pending_span_threads.delete(trace_id) || [] }
92
+ pending << span_thread if span_thread
93
+ pending.each { |t| t.join(5) }
94
+
83
95
  send_trace_completion(
84
96
  trace_function_key:,
85
97
  trace_id:,
86
98
  started_at:,
87
99
  ended_at:
88
100
  )
101
+ else
102
+ @pending_span_mutex.synchronize do
103
+ @pending_span_threads[trace_id] << span_thread if span_thread && @pending_span_threads.key?(trace_id)
104
+ end
89
105
  end
90
106
  rescue Exception # rubocop:disable Lint/RescueException
91
107
  # Silently ignore — user's result/exception takes priority
@@ -141,7 +157,7 @@ module Simforge
141
157
  end
142
158
 
143
159
  def send_span(trace_function_key:, trace_id:, span_id:, parent_span_id:,
144
- span_name:, span_type:, function_name:, contexts:, args:, kwargs:, result:, error:,
160
+ span_name:, span_type:, function_name:, contexts:, prompt:, args:, kwargs:, result:, error:,
145
161
  started_at:, ended_at:)
146
162
  # Human-readable JSON (input/output fields)
147
163
  human_inputs = Serialize.serialize_inputs(args, kwargs)
@@ -163,6 +179,7 @@ module Simforge
163
179
  span_data["output_serialized"] = marshalled_output if marshalled_output
164
180
  span_data["error"] = error if error
165
181
  span_data["contexts"] = contexts if contexts&.any?
182
+ span_data["prompt"] = prompt if prompt
166
183
 
167
184
  raw_span = {
168
185
  "id" => span_id,
@@ -179,7 +196,7 @@ module Simforge
179
196
  "sourceTraceId" => trace_id,
180
197
  "traceFunctionKey" => trace_function_key,
181
198
  "rawSpan" => raw_span
182
- )
199
+ ) # Returns the background thread
183
200
  end
184
201
  end
185
202
  end
@@ -57,7 +57,8 @@ module Simforge
57
57
  raise last_error
58
58
  end
59
59
 
60
- # Send an external span (fire-and-forget in background thread).
60
+ # Send an external span in a background thread.
61
+ # Returns the thread for callers that need to await completion.
61
62
  def send_external_span(payload)
62
63
  merged = payload.merge("sdkVersion" => VERSION)
63
64
 
@@ -92,16 +93,22 @@ module Simforge
92
93
 
93
94
  class << self
94
95
  # Run a block in a background thread with tracking.
96
+ # Returns the thread for callers that need to join on it.
95
97
  def _run_in_background(&block)
96
98
  thread = Thread.new do
97
99
  block.call
98
- rescue
99
- # Silently ignore failures in background spans
100
+ rescue => e
101
+ begin
102
+ warn "Simforge: Failed to send request: #{e.message}"
103
+ rescue
104
+ # Never crash the host app
105
+ end
100
106
  ensure
101
107
  @pending_threads_mutex.synchronize { @pending_threads.delete(Thread.current) }
102
108
  end
103
109
 
104
110
  @pending_threads_mutex.synchronize { @pending_threads << thread }
111
+ thread
105
112
  end
106
113
 
107
114
  # Wait for all pending background threads to complete.
@@ -25,6 +25,19 @@ module Simforge
25
25
  rescue
26
26
  # Silently ignore - never crash the host app
27
27
  end
28
+
29
+ # Set the prompt for this span.
30
+ # The prompt is stored in span_data.prompt. Calling multiple times
31
+ # overwrites the previous value.
32
+ #
33
+ # @param prompt [String] the prompt string to store
34
+ def set_prompt(prompt)
35
+ return unless prompt.is_a?(String)
36
+
37
+ @span_state[:prompt] = prompt
38
+ rescue
39
+ # Silently ignore - never crash the host app
40
+ end
28
41
  end
29
42
 
30
43
  # Handle to the current active trace, allowing trace-level context to be set.
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Simforge
4
- VERSION = "0.6.0"
4
+ VERSION = "0.8.0"
5
5
  end
data/lib/simforge.rb CHANGED
@@ -19,6 +19,10 @@ module Simforge
19
19
  def add_context(_context)
20
20
  # No-op
21
21
  end
22
+
23
+ def set_prompt(_prompt)
24
+ # No-op
25
+ end
22
26
  end
23
27
 
24
28
  # No-op trace handle returned when outside a span context.
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: simforge
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.0
4
+ version: 0.8.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Harvest Team
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-02-13 00:00:00.000000000 Z
11
+ date: 2026-02-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake