payloop 0.2.0 → 0.4.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/CHANGELOG.md +11 -0
- data/lib/payloop/api/invocation.rb +2 -4
- data/lib/payloop/attribution.rb +4 -2
- data/lib/payloop/client.rb +7 -0
- data/lib/payloop/config.rb +23 -2
- data/lib/payloop/errors.rb +3 -0
- data/lib/payloop/sentinel.rb +10 -1
- data/lib/payloop/version.rb +1 -1
- data/lib/payloop/wrappers/anthropic.rb +10 -3
- data/lib/payloop/wrappers/base.rb +31 -26
- data/lib/payloop/wrappers/constants.rb +1 -0
- data/lib/payloop/wrappers/geminiai.rb +22 -7
- data/lib/payloop/wrappers/google.rb +14 -5
- data/lib/payloop/wrappers/groq.rb +295 -0
- data/lib/payloop/wrappers/openai.rb +144 -6
- data/lib/payloop/wrappers/ruby_llm.rb +18 -4
- data/lib/payloop.rb +1 -0
- data/sig/payloop/api/base.rbs +24 -0
- data/sig/payloop/api/invocation.rbs +16 -0
- data/sig/payloop/api/sentinel.rbs +9 -0
- data/sig/payloop/api/workflow.rbs +15 -0
- data/sig/payloop/api/workflows.rbs +16 -0
- data/sig/payloop/attribution.rbs +17 -0
- data/sig/payloop/client.rbs +29 -0
- data/sig/payloop/collector.rbs +20 -0
- data/sig/payloop/config.rbs +33 -0
- data/sig/payloop/errors.rbs +28 -0
- data/sig/payloop/sentinel.rbs +17 -0
- data/sig/payloop/version.rbs +5 -0
- data/sig/payloop/wrappers/anthropic.rbs +18 -0
- data/sig/payloop/wrappers/base.rbs +21 -0
- data/sig/payloop/wrappers/constants.rbs +10 -0
- data/sig/payloop/wrappers/geminiai.rbs +19 -0
- data/sig/payloop/wrappers/google.rbs +19 -0
- data/sig/payloop/wrappers/groq.rbs +22 -0
- data/sig/payloop/wrappers/openai.rbs +19 -0
- data/sig/payloop/wrappers/ruby_llm.rbs +21 -0
- metadata +23 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: dc7fddc9f323048d0b9930cfa546f569407e82f6114b88c9700e0d635ac04df0
|
|
4
|
+
data.tar.gz: 306b03a60258ad5ea53ad8fc1cc97efd9c9959c4b517a17c04a1b37c7fde2084
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 96b3bcb520e6e0e062bad34f56f1a5487c466881b7e29eaf4e2b33d98ecda3a4188ec52db19242f6bd10e45cecff1b029c62e223a7ae7664d630f92454ceeed0
|
|
7
|
+
data.tar.gz: 576e9946a609e38d998979ce71480c08513e263ac2b6fa0421303ba9c26b228b0035c3acbc9898a375f923d1a80ab577b69d2f6edce8e3affad367a00c4562eb
|
data/CHANGELOG.md
CHANGED
|
@@ -51,3 +51,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
51
51
|
|
|
52
52
|
### Added
|
|
53
53
|
- Support for the RubyLLM library
|
|
54
|
+
|
|
55
|
+
## [0.3.0] - 2026-04-15
|
|
56
|
+
|
|
57
|
+
### Added
|
|
58
|
+
- Support OpenAI Responses API (responses.create)
|
|
59
|
+
|
|
60
|
+
## [0.4.0] - 2026-05-19
|
|
61
|
+
|
|
62
|
+
### Added
|
|
63
|
+
- Support for the Groq Ruby client (`groq` gem, drnic/groq-ruby), including streaming.
|
|
64
|
+
- RBS type signatures shipped with the gem.
|
|
@@ -7,7 +7,7 @@ module Payloop
|
|
|
7
7
|
# API client for workflow invocation operations
|
|
8
8
|
class Invocation < Base
|
|
9
9
|
def initialize(api_url, api_key, timeout)
|
|
10
|
-
super
|
|
10
|
+
super
|
|
11
11
|
@attribution = nil
|
|
12
12
|
end
|
|
13
13
|
|
|
@@ -35,9 +35,7 @@ module Payloop
|
|
|
35
35
|
}
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
-
if date_end
|
|
39
|
-
body[:date][:end] = format_date(date_end)
|
|
40
|
-
end
|
|
38
|
+
body[:date][:end] = format_date(date_end) if date_end
|
|
41
39
|
|
|
42
40
|
body[:attribution] = @attribution if @attribution
|
|
43
41
|
|
data/lib/payloop/attribution.rb
CHANGED
|
@@ -38,7 +38,8 @@ module Payloop
|
|
|
38
38
|
|
|
39
39
|
value_str = value.to_s
|
|
40
40
|
if value_str.length > 100
|
|
41
|
-
raise ValidationError,
|
|
41
|
+
raise ValidationError,
|
|
42
|
+
"parent_id cannot exceed 100 characters (got #{value_str.length})"
|
|
42
43
|
end
|
|
43
44
|
|
|
44
45
|
value_str
|
|
@@ -49,7 +50,8 @@ module Payloop
|
|
|
49
50
|
|
|
50
51
|
value_str = value.to_s
|
|
51
52
|
if value_str.length > 100
|
|
52
|
-
raise ValidationError,
|
|
53
|
+
raise ValidationError,
|
|
54
|
+
"#{field_name} cannot exceed 100 characters (got #{value_str.length})"
|
|
53
55
|
end
|
|
54
56
|
|
|
55
57
|
value_str
|
data/lib/payloop/client.rb
CHANGED
|
@@ -52,6 +52,13 @@ module Payloop
|
|
|
52
52
|
@ruby_llm ||= Wrappers::RubyLLM.new(@config, @collector, @sentinel)
|
|
53
53
|
end
|
|
54
54
|
|
|
55
|
+
# Groq provider wrapper (groq gem, drnic/groq-ruby)
|
|
56
|
+
# Groq hosts third-party models (Meta Llama, OpenAI gpt-oss, Qwen, etc.).
|
|
57
|
+
# Telemetry sets provider="groq" and title from the model-ID prefix.
|
|
58
|
+
def groq
|
|
59
|
+
@groq ||= Wrappers::Groq.new(@config, @collector, @sentinel)
|
|
60
|
+
end
|
|
61
|
+
|
|
55
62
|
# Set attribution for cost tracking
|
|
56
63
|
def attribution(parent_id:, parent_name: nil, subsidiary_id: nil, subsidiary_name: nil)
|
|
57
64
|
attr = Attribution.new(
|
data/lib/payloop/config.rb
CHANGED
|
@@ -9,8 +9,16 @@ module Payloop
|
|
|
9
9
|
|
|
10
10
|
def initialize(api_key: nil, collector_url: nil, api_url: nil, timeout: nil)
|
|
11
11
|
@api_key = api_key
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
# URL precedence: constructor arg > PAYLOOP_*_URL_BASE env var > hardcoded
|
|
13
|
+
# prod default. Matches the JS SDK so an integration-test run pointed at
|
|
14
|
+
# staging or a local backend only requires setting the env vars in
|
|
15
|
+
# `.env` — no spec changes needed.
|
|
16
|
+
@collector_url = collector_url ||
|
|
17
|
+
nonempty_env("PAYLOOP_COLLECTOR_URL_BASE") ||
|
|
18
|
+
"https://collector.trypayloop.com"
|
|
19
|
+
@api_url = api_url ||
|
|
20
|
+
nonempty_env("PAYLOOP_API_URL_BASE") ||
|
|
21
|
+
"https://api.trypayloop.com"
|
|
14
22
|
@timeout = timeout || 5
|
|
15
23
|
@version = Payloop::VERSION
|
|
16
24
|
@attribution = Concurrent::AtomicReference.new(nil)
|
|
@@ -54,5 +62,18 @@ module Payloop
|
|
|
54
62
|
def new_transaction
|
|
55
63
|
@tx_uuid.set(SecureRandom.uuid)
|
|
56
64
|
end
|
|
65
|
+
|
|
66
|
+
private
|
|
67
|
+
|
|
68
|
+
# Returns ENV[name] unless it's missing or empty. We can't use the JS-style
|
|
69
|
+
# `ENV[name] || default` because Ruby treats empty strings as truthy — and
|
|
70
|
+
# the `.env.example` ships with blank values for the URL keys, so naïve
|
|
71
|
+
# truthiness would route every request to "".
|
|
72
|
+
def nonempty_env(name)
|
|
73
|
+
value = ENV.fetch(name, nil)
|
|
74
|
+
return nil if value.nil? || value.empty?
|
|
75
|
+
|
|
76
|
+
value
|
|
77
|
+
end
|
|
57
78
|
end
|
|
58
79
|
end
|
data/lib/payloop/errors.rb
CHANGED
data/lib/payloop/sentinel.rb
CHANGED
|
@@ -13,7 +13,10 @@ module Payloop
|
|
|
13
13
|
self
|
|
14
14
|
end
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
# Intentional explicit `set_*` verb prefix — matches JS/Python SDK naming
|
|
17
|
+
# so callers porting code between SDKs see the same surface. See CLAUDE.md
|
|
18
|
+
# "Python SDK is the source of truth" / known divergences.
|
|
19
|
+
def set_secs_irrelevant_request_timeout(timeout) # rubocop:disable Naming/AccessorMethodName
|
|
17
20
|
raise TypeError, "timeout must be a Numeric" unless timeout.is_a?(Numeric)
|
|
18
21
|
raise ArgumentError, "timeout must be greater than 0" unless timeout.positive?
|
|
19
22
|
|
|
@@ -50,6 +53,12 @@ module Payloop
|
|
|
50
53
|
version: version
|
|
51
54
|
},
|
|
52
55
|
request: normalize_request(request)
|
|
56
|
+
},
|
|
57
|
+
meta: {
|
|
58
|
+
sdk: {
|
|
59
|
+
client: "ruby",
|
|
60
|
+
version: @config.version
|
|
61
|
+
}
|
|
53
62
|
}
|
|
54
63
|
}
|
|
55
64
|
|
data/lib/payloop/version.rb
CHANGED
|
@@ -56,11 +56,16 @@ module Payloop
|
|
|
56
56
|
extend Base unless singleton_class.include?(Base)
|
|
57
57
|
|
|
58
58
|
start_time = Time.now
|
|
59
|
+
version = defined?(::Anthropic::VERSION) ? ::Anthropic::VERSION : nil
|
|
59
60
|
|
|
60
61
|
# Extract parameters for sentinel and analytics
|
|
61
62
|
params = kwargs.any? ? kwargs : (args.first || {})
|
|
62
63
|
sentinel = instance_variable_get(:@payloop_sentinel)
|
|
63
|
-
sentinel&.raise_if_irrelevant!(
|
|
64
|
+
sentinel&.raise_if_irrelevant!(
|
|
65
|
+
title: ANTHROPIC_CLIENT_TITLE,
|
|
66
|
+
request: params,
|
|
67
|
+
version: version
|
|
68
|
+
)
|
|
64
69
|
|
|
65
70
|
# Call original method
|
|
66
71
|
response = if kwargs.any?
|
|
@@ -77,7 +82,8 @@ module Payloop
|
|
|
77
82
|
response: response,
|
|
78
83
|
start_time: start_time,
|
|
79
84
|
end_time: Time.now,
|
|
80
|
-
title: ANTHROPIC_CLIENT_TITLE
|
|
85
|
+
title: ANTHROPIC_CLIENT_TITLE,
|
|
86
|
+
version: version
|
|
81
87
|
)
|
|
82
88
|
|
|
83
89
|
response
|
|
@@ -94,7 +100,8 @@ module Payloop
|
|
|
94
100
|
error: e,
|
|
95
101
|
start_time: start_time,
|
|
96
102
|
end_time: Time.now,
|
|
97
|
-
title: ANTHROPIC_CLIENT_TITLE
|
|
103
|
+
title: ANTHROPIC_CLIENT_TITLE,
|
|
104
|
+
version: version
|
|
98
105
|
)
|
|
99
106
|
|
|
100
107
|
raise e
|
|
@@ -44,7 +44,8 @@ module Payloop
|
|
|
44
44
|
end
|
|
45
45
|
|
|
46
46
|
def payloop_submit_analytics(method:, args:, kwargs:, response:, start_time:,
|
|
47
|
-
end_time:, provider: nil, title: nil
|
|
47
|
+
end_time:, provider: nil, title: nil, version: nil,
|
|
48
|
+
status: "succeeded", exception: nil)
|
|
48
49
|
collector = instance_variable_get(:@payloop_collector)
|
|
49
50
|
config = instance_variable_get(:@payloop_config)
|
|
50
51
|
|
|
@@ -56,34 +57,38 @@ module Payloop
|
|
|
56
57
|
start_time: start_time,
|
|
57
58
|
end_time: end_time,
|
|
58
59
|
config: config,
|
|
59
|
-
status:
|
|
60
|
+
status: status,
|
|
60
61
|
provider: provider,
|
|
61
|
-
title: title
|
|
62
|
+
title: title,
|
|
63
|
+
version: version,
|
|
64
|
+
exception: exception
|
|
62
65
|
)
|
|
63
66
|
|
|
64
67
|
collector.submit_async(payload)
|
|
65
68
|
end
|
|
66
69
|
|
|
70
|
+
# Submit a failed-call payload to the collector. `response:` is optional —
|
|
71
|
+
# when omitted, the default `{ error:, class: }` shape is used. Wrappers
|
|
72
|
+
# that preserve richer data on error (e.g. Groq's streaming wrapper
|
|
73
|
+
# merging accumulated chunks with error info via build_error_response)
|
|
74
|
+
# pass it explicitly; the backend extractor reads whatever fields it can
|
|
75
|
+
# from the supplied hash and falls back gracefully on missing keys.
|
|
67
76
|
def payloop_submit_error_analytics(method:, args:, kwargs:, error:, start_time:,
|
|
68
|
-
end_time:, provider: nil, title: nil
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
query: extract_query(method, args, kwargs),
|
|
76
|
-
response: { error: error.message, class: error.class.name },
|
|
77
|
+
end_time:, provider: nil, title: nil, version: nil,
|
|
78
|
+
response: nil)
|
|
79
|
+
payloop_submit_analytics(
|
|
80
|
+
method: method,
|
|
81
|
+
args: args,
|
|
82
|
+
kwargs: kwargs,
|
|
83
|
+
response: response || { error: error.message, class: error.class.name },
|
|
77
84
|
start_time: start_time,
|
|
78
85
|
end_time: end_time,
|
|
79
|
-
config: config,
|
|
80
|
-
status: "failed",
|
|
81
86
|
provider: provider,
|
|
82
87
|
title: title,
|
|
88
|
+
version: version,
|
|
89
|
+
status: "failed",
|
|
83
90
|
exception: error.message
|
|
84
91
|
)
|
|
85
|
-
|
|
86
|
-
collector.submit_async(payload)
|
|
87
92
|
end
|
|
88
93
|
|
|
89
94
|
def payloop_merge_streaming_chunk(accumulated, chunk)
|
|
@@ -115,14 +120,14 @@ module Payloop
|
|
|
115
120
|
next unless choice.is_a?(Hash) && choice.key?("delta")
|
|
116
121
|
|
|
117
122
|
delta = choice["delta"]
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
123
|
+
next unless delta.is_a?(Hash)
|
|
124
|
+
|
|
125
|
+
# Add missing keys with nil values to match Python SDK
|
|
126
|
+
delta["role"] ||= nil unless delta.key?("role")
|
|
127
|
+
delta["content"] ||= nil unless delta.key?("content")
|
|
128
|
+
delta["refusal"] ||= nil unless delta.key?("refusal")
|
|
129
|
+
delta["tool_calls"] ||= nil unless delta.key?("tool_calls")
|
|
130
|
+
delta["function_call"] ||= nil unless delta.key?("function_call")
|
|
126
131
|
end
|
|
127
132
|
end
|
|
128
133
|
|
|
@@ -215,14 +220,14 @@ module Payloop
|
|
|
215
220
|
# - conversion.response - Holds the response from the LLM. This is different for each LLM,
|
|
216
221
|
# and the different services handle them on the backend.
|
|
217
222
|
def build_payload(query:, response:, start_time:, end_time:, config:, status:,
|
|
218
|
-
provider: nil, title: nil, exception: nil)
|
|
223
|
+
provider: nil, title: nil, version: nil, exception: nil)
|
|
219
224
|
{
|
|
220
225
|
attribution: config.attribution&.to_h,
|
|
221
226
|
conversation: {
|
|
222
227
|
client: {
|
|
223
228
|
provider: provider,
|
|
224
229
|
title: title,
|
|
225
|
-
version:
|
|
230
|
+
version: version
|
|
226
231
|
},
|
|
227
232
|
query: query,
|
|
228
233
|
response: response
|
|
@@ -37,7 +37,8 @@ module Payloop
|
|
|
37
37
|
return if client.respond_to?(:generate_content) && client.respond_to?(:stream_generate_content)
|
|
38
38
|
|
|
39
39
|
raise RegistrationError,
|
|
40
|
-
"Client does not appear to be a valid Gemini AI client
|
|
40
|
+
"Client does not appear to be a valid Gemini AI client " \
|
|
41
|
+
"(missing generate_content or stream_generate_content method)"
|
|
41
42
|
end
|
|
42
43
|
|
|
43
44
|
def wrap_generate_content_method(client)
|
|
@@ -57,9 +58,14 @@ module Payloop
|
|
|
57
58
|
end
|
|
58
59
|
|
|
59
60
|
start_time = Time.now
|
|
61
|
+
version = defined?(::Gemini::GEM) && ::Gemini::GEM.is_a?(Hash) ? ::Gemini::GEM[:version] : nil
|
|
60
62
|
|
|
61
63
|
sentinel = instance_variable_get(:@payloop_sentinel)
|
|
62
|
-
sentinel&.raise_if_irrelevant!(
|
|
64
|
+
sentinel&.raise_if_irrelevant!(
|
|
65
|
+
title: GOOGLE_CLIENT_TITLE,
|
|
66
|
+
request: parameters,
|
|
67
|
+
version: version
|
|
68
|
+
)
|
|
63
69
|
|
|
64
70
|
# Call original method
|
|
65
71
|
response = original_generate_content(*args, **kwargs, &block)
|
|
@@ -72,7 +78,8 @@ module Payloop
|
|
|
72
78
|
response: response,
|
|
73
79
|
start_time: start_time,
|
|
74
80
|
end_time: Time.now,
|
|
75
|
-
title: GOOGLE_CLIENT_TITLE
|
|
81
|
+
title: GOOGLE_CLIENT_TITLE,
|
|
82
|
+
version: version
|
|
76
83
|
)
|
|
77
84
|
|
|
78
85
|
response
|
|
@@ -87,7 +94,8 @@ module Payloop
|
|
|
87
94
|
error: e,
|
|
88
95
|
start_time: start_time,
|
|
89
96
|
end_time: Time.now,
|
|
90
|
-
title: GOOGLE_CLIENT_TITLE
|
|
97
|
+
title: GOOGLE_CLIENT_TITLE,
|
|
98
|
+
version: version
|
|
91
99
|
)
|
|
92
100
|
|
|
93
101
|
raise e
|
|
@@ -116,9 +124,14 @@ module Payloop
|
|
|
116
124
|
end
|
|
117
125
|
|
|
118
126
|
start_time = Time.now
|
|
127
|
+
version = defined?(::Gemini::GEM) && ::Gemini::GEM.is_a?(Hash) ? ::Gemini::GEM[:version] : nil
|
|
119
128
|
|
|
120
129
|
sentinel = instance_variable_get(:@payloop_sentinel)
|
|
121
|
-
sentinel&.raise_if_irrelevant!(
|
|
130
|
+
sentinel&.raise_if_irrelevant!(
|
|
131
|
+
title: GOOGLE_CLIENT_TITLE,
|
|
132
|
+
request: parameters,
|
|
133
|
+
version: version
|
|
134
|
+
)
|
|
122
135
|
|
|
123
136
|
# Call original method with wrapped block
|
|
124
137
|
response = original_stream_generate_content(*args, **kwargs, &block)
|
|
@@ -131,7 +144,8 @@ module Payloop
|
|
|
131
144
|
response: response,
|
|
132
145
|
start_time: start_time,
|
|
133
146
|
end_time: Time.now,
|
|
134
|
-
title: GOOGLE_CLIENT_TITLE
|
|
147
|
+
title: GOOGLE_CLIENT_TITLE,
|
|
148
|
+
version: version
|
|
135
149
|
)
|
|
136
150
|
|
|
137
151
|
response
|
|
@@ -146,7 +160,8 @@ module Payloop
|
|
|
146
160
|
error: e,
|
|
147
161
|
start_time: start_time,
|
|
148
162
|
end_time: Time.now,
|
|
149
|
-
title: GOOGLE_CLIENT_TITLE
|
|
163
|
+
title: GOOGLE_CLIENT_TITLE,
|
|
164
|
+
version: version
|
|
150
165
|
)
|
|
151
166
|
|
|
152
167
|
raise e
|
|
@@ -49,8 +49,10 @@ module Payloop
|
|
|
49
49
|
return if ::Google::Genai::Types::GenerateContentResponse.instance_variable_defined?(:@_payloop_patched)
|
|
50
50
|
|
|
51
51
|
::Google::Genai::Types::GenerateContentResponse.class_eval do
|
|
52
|
-
#
|
|
53
|
-
|
|
52
|
+
# camelCase intentional — these names must match Google's API response
|
|
53
|
+
# keys verbatim so the patched accessors survive serialization through
|
|
54
|
+
# `Base#extract_response`.
|
|
55
|
+
attr_accessor :modelVersion, :usageMetadata, :responseId, :createTime # rubocop:disable Naming/MethodName
|
|
54
56
|
end
|
|
55
57
|
|
|
56
58
|
::Google::Genai::Types::GenerateContentResponse.instance_variable_set(:@_payloop_patched, true)
|
|
@@ -74,12 +76,17 @@ module Payloop
|
|
|
74
76
|
extend Base unless singleton_class.include?(Base)
|
|
75
77
|
|
|
76
78
|
start_time = Time.now
|
|
79
|
+
version = defined?(::Google::Genai::VERSION) ? ::Google::Genai::VERSION : nil
|
|
77
80
|
|
|
78
81
|
# Extract parameters for analytics
|
|
79
82
|
params = kwargs.any? ? kwargs : (args.first || {})
|
|
80
83
|
|
|
81
84
|
sentinel = instance_variable_get(:@payloop_sentinel)
|
|
82
|
-
sentinel&.raise_if_irrelevant!(
|
|
85
|
+
sentinel&.raise_if_irrelevant!(
|
|
86
|
+
title: GOOGLE_CLIENT_TITLE,
|
|
87
|
+
request: params,
|
|
88
|
+
version: version
|
|
89
|
+
)
|
|
83
90
|
|
|
84
91
|
# Call original method
|
|
85
92
|
response = if kwargs.any?
|
|
@@ -96,7 +103,8 @@ module Payloop
|
|
|
96
103
|
response: response,
|
|
97
104
|
start_time: start_time,
|
|
98
105
|
end_time: Time.now,
|
|
99
|
-
title: GOOGLE_CLIENT_TITLE
|
|
106
|
+
title: GOOGLE_CLIENT_TITLE,
|
|
107
|
+
version: version
|
|
100
108
|
)
|
|
101
109
|
|
|
102
110
|
response
|
|
@@ -113,7 +121,8 @@ module Payloop
|
|
|
113
121
|
error: e,
|
|
114
122
|
start_time: start_time,
|
|
115
123
|
end_time: Time.now,
|
|
116
|
-
title: GOOGLE_CLIENT_TITLE
|
|
124
|
+
title: GOOGLE_CLIENT_TITLE,
|
|
125
|
+
version: version
|
|
117
126
|
)
|
|
118
127
|
|
|
119
128
|
raise e
|