activeagent 0.4.0 → 0.4.1
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/active_agent/action_prompt/base.rb +50 -48
- data/lib/active_agent/base.rb +2 -2
- data/lib/active_agent/version.rb +1 -1
- data/lib/generators/active_agent/agent_generator.rb +5 -3
- data/lib/generators/active_agent/install_generator.rb +2 -9
- data/lib/generators/active_agent/templates/agent.rb.tt +1 -1
- data/lib/generators/erb/agent_generator.rb +43 -0
- data/lib/generators/erb/install_generator.rb +16 -0
- data/lib/generators/erb/templates/application_agent.rb.tt +7 -0
- data/lib/generators/erb/templates/layout.html.erb.tt +1 -0
- data/lib/generators/erb/templates/layout.text.erb.tt +1 -0
- data/lib/generators/erb/templates/view.html.erb.tt +5 -0
- data/lib/generators/erb/templates/view.text.erb.tt +3 -0
- data/lib/generators/test_unit/agent_generator.rb +30 -0
- data/lib/generators/test_unit/install_generator.rb +13 -0
- data/lib/generators/test_unit/templates/functional_test.rb.tt +20 -0
- data/lib/generators/test_unit/templates/preview.rb.tt +14 -0
- metadata +12 -7
- data/lib/generators/active_agent/templates/action.html.erb.tt +0 -0
- data/lib/generators/active_agent/templates/action.json.jbuilder.tt +0 -14
- data/lib/generators/active_agent/templates/agent.html.erb +0 -1
- data/lib/generators/active_agent/templates/agent.text.erb +0 -1
- data/lib/generators/active_agent/templates/agent_spec.rb.tt +0 -0
- data/lib/generators/active_agent/templates/agent_test.rb.tt +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c93bac60dcbdb44bfc6461435f84e79499682db83a93ea07526a5b546c2a1b63
|
4
|
+
data.tar.gz: c3a7a995862ef152661b1ede6c012e17747c190853cdc501e0a0f99445c7fe40
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5e2836e13ca5d353d221a33643c7c101320e2615982dceccf4dfdf25c9e9fe9bbdeb48503aa70d1a21ed1dd8cfdab0ec76348342dbbd29dc5cfb59f9306266f9
|
7
|
+
data.tar.gz: edaddc4339c00242935f0ba70fdc95f98547c12ccd6d4b4da9cd4081dd051e3521c6b4bdb5745fe6e7a382e462024b0dc0aec12eacd730b2ebc5f12a81315d35
|
@@ -173,7 +173,7 @@ module ActiveAgent
|
|
173
173
|
end
|
174
174
|
end
|
175
175
|
|
176
|
-
attr_internal :
|
176
|
+
attr_internal :context
|
177
177
|
|
178
178
|
def agent_stream
|
179
179
|
proc do |message, delta, stop|
|
@@ -184,8 +184,8 @@ module ActiveAgent
|
|
184
184
|
end
|
185
185
|
|
186
186
|
def embed
|
187
|
-
|
188
|
-
generation_provider.embed(
|
187
|
+
context.options.merge(options)
|
188
|
+
generation_provider.embed(context) if context && generation_provider
|
189
189
|
handle_response(generation_provider.response)
|
190
190
|
end
|
191
191
|
|
@@ -194,34 +194,34 @@ module ActiveAgent
|
|
194
194
|
def embed
|
195
195
|
agent_class = ActiveAgent::Base.descendants.first
|
196
196
|
agent = agent_class.new
|
197
|
-
agent.
|
197
|
+
agent.context = ActiveAgent::ActionPrompt::Prompt.new(message: self)
|
198
198
|
agent.embed
|
199
199
|
self
|
200
200
|
end
|
201
201
|
end
|
202
202
|
|
203
|
-
# Make
|
204
|
-
# attr_accessor :
|
203
|
+
# Make context accessible for chaining
|
204
|
+
# attr_accessor :context
|
205
205
|
|
206
206
|
def perform_generation
|
207
|
-
|
207
|
+
context.options.merge(options)
|
208
208
|
if (action_methods - ActiveAgent::Base.descendants.first.action_methods).include? action_name
|
209
|
-
|
210
|
-
|
209
|
+
context.message = context.messages.last
|
210
|
+
context.actions = []
|
211
211
|
end
|
212
|
-
generation_provider.generate(
|
212
|
+
generation_provider.generate(context) if context && generation_provider
|
213
213
|
handle_response(generation_provider.response)
|
214
214
|
end
|
215
215
|
|
216
216
|
def handle_response(response)
|
217
217
|
return response unless response.message.requested_actions.present?
|
218
218
|
perform_actions(requested_actions: response.message.requested_actions)
|
219
|
-
|
219
|
+
update_context(response)
|
220
220
|
end
|
221
221
|
|
222
|
-
def
|
223
|
-
|
224
|
-
ActiveAgent::GenerationProvider::Response.new(prompt:
|
222
|
+
def update_context(response)
|
223
|
+
context.message = context.messages.last
|
224
|
+
ActiveAgent::GenerationProvider::Response.new(prompt: context)
|
225
225
|
end
|
226
226
|
|
227
227
|
def perform_actions(requested_actions:)
|
@@ -231,20 +231,20 @@ module ActiveAgent
|
|
231
231
|
end
|
232
232
|
|
233
233
|
def perform_action(action)
|
234
|
-
current_context =
|
234
|
+
current_context = context.clone
|
235
235
|
process(action.name, *action.params)
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
current_context.messages <<
|
241
|
-
self.
|
236
|
+
context.messages.last.role = :tool
|
237
|
+
context.messages.last.action_id = action.id
|
238
|
+
context.messages.last.action_name = action.name
|
239
|
+
context.messages.last.generation_id = action.id
|
240
|
+
current_context.messages << context.messages.last
|
241
|
+
self.context = current_context
|
242
242
|
end
|
243
243
|
|
244
244
|
def initialize
|
245
245
|
super
|
246
246
|
@_prompt_was_called = false
|
247
|
-
@
|
247
|
+
@_context = ActiveAgent::ActionPrompt::Prompt.new(instructions: options[:instructions], options: options)
|
248
248
|
end
|
249
249
|
|
250
250
|
def process(method_name, *args) # :nodoc:
|
@@ -256,7 +256,7 @@ module ActiveAgent
|
|
256
256
|
|
257
257
|
ActiveSupport::Notifications.instrument("process.active_agent", payload) do
|
258
258
|
super
|
259
|
-
@
|
259
|
+
@_context = ActiveAgent::ActionPrompt::Prompt.new unless @_prompt_was_called
|
260
260
|
end
|
261
261
|
end
|
262
262
|
ruby2_keywords(:process)
|
@@ -286,24 +286,24 @@ module ActiveAgent
|
|
286
286
|
|
287
287
|
def headers(args = nil)
|
288
288
|
if args
|
289
|
-
@
|
289
|
+
@_context.headers(args)
|
290
290
|
else
|
291
|
-
@
|
291
|
+
@_context
|
292
292
|
end
|
293
293
|
end
|
294
294
|
|
295
295
|
def prompt_with(*)
|
296
|
-
|
296
|
+
context.update_context(*)
|
297
297
|
end
|
298
298
|
|
299
299
|
def prompt(headers = {}, &block)
|
300
|
-
return
|
300
|
+
return context if @_prompt_was_called && headers.blank? && !block
|
301
301
|
content_type = headers[:content_type]
|
302
302
|
headers = apply_defaults(headers)
|
303
|
-
|
304
|
-
|
303
|
+
context.messages = headers[:messages] || []
|
304
|
+
context.context_id = headers[:context_id]
|
305
305
|
|
306
|
-
|
306
|
+
context.charset = charset = headers[:charset]
|
307
307
|
|
308
308
|
if headers[:message].present? && headers[:message].is_a?(ActiveAgent::ActionPrompt::Message)
|
309
309
|
headers[:body] = headers[:message].content
|
@@ -314,25 +314,27 @@ module ActiveAgent
|
|
314
314
|
end
|
315
315
|
|
316
316
|
# wrap_generation_behavior!(headers[:generation_method], headers[:generation_method_options])
|
317
|
-
#
|
317
|
+
# assign_headers_to_context(context, headers)
|
318
318
|
responses = collect_responses(headers, &block)
|
319
319
|
|
320
320
|
@_prompt_was_called = true
|
321
321
|
|
322
|
-
create_parts_from_responses(
|
322
|
+
create_parts_from_responses(context, responses)
|
323
323
|
|
324
|
-
|
325
|
-
|
326
|
-
|
324
|
+
context.content_type = set_content_type(context, content_type, headers[:content_type])
|
325
|
+
context.charset = charset
|
326
|
+
context.actions = headers[:actions] || action_schemas
|
327
327
|
|
328
|
-
|
328
|
+
context
|
329
|
+
end
|
330
|
+
|
331
|
+
def action_methods
|
332
|
+
super - ActiveAgent::Base.public_instance_methods(false).map(&:to_s)
|
329
333
|
end
|
330
334
|
|
331
335
|
def action_schemas
|
332
336
|
action_methods.map do |action|
|
333
|
-
|
334
|
-
JSON.parse render_to_string(locals: { action_name: action }, action: action, formats: :json)
|
335
|
-
end
|
337
|
+
JSON.parse render_to_string(locals: { action_name: action }, action: action, formats: :json)
|
336
338
|
end.compact
|
337
339
|
end
|
338
340
|
|
@@ -342,7 +344,7 @@ module ActiveAgent
|
|
342
344
|
if user_content_type.present?
|
343
345
|
user_content_type
|
344
346
|
else
|
345
|
-
|
347
|
+
context.content_type || class_default
|
346
348
|
end
|
347
349
|
end
|
348
350
|
|
@@ -373,11 +375,11 @@ module ActiveAgent
|
|
373
375
|
end
|
374
376
|
end
|
375
377
|
|
376
|
-
def
|
378
|
+
def assign_headers_to_context(context, headers)
|
377
379
|
assignable = headers.except(:parts_order, :content_type, :body, :role, :template_name,
|
378
380
|
:template_path, :delivery_method, :delivery_method_options)
|
379
381
|
|
380
|
-
assignable.each { |k, v|
|
382
|
+
assignable.each { |k, v| context.send(k, v) if context.respond_to?(k) }
|
381
383
|
end
|
382
384
|
|
383
385
|
def collect_responses(headers, &)
|
@@ -426,24 +428,24 @@ module ActiveAgent
|
|
426
428
|
end
|
427
429
|
end
|
428
430
|
|
429
|
-
def create_parts_from_responses(
|
431
|
+
def create_parts_from_responses(context, responses)
|
430
432
|
if responses.size > 1
|
431
433
|
# prompt_container = ActiveAgent::ActionPrompt::Prompt.new
|
432
434
|
# prompt_container.content_type = "multipart/alternative"
|
433
|
-
responses.each { |r| insert_part(
|
434
|
-
#
|
435
|
+
responses.each { |r| insert_part(context, r, context.charset) }
|
436
|
+
# context.add_part(prompt_container)
|
435
437
|
else
|
436
|
-
responses.each { |r| insert_part(
|
438
|
+
responses.each { |r| insert_part(context, r, context.charset) }
|
437
439
|
end
|
438
440
|
end
|
439
441
|
|
440
|
-
def insert_part(
|
442
|
+
def insert_part(context, response, charset)
|
441
443
|
message = ActiveAgent::ActionPrompt::Message.new(
|
442
444
|
content: response[:body],
|
443
445
|
content_type: response[:content_type],
|
444
446
|
charset: charset
|
445
447
|
)
|
446
|
-
|
448
|
+
context.add_part(message)
|
447
449
|
end
|
448
450
|
|
449
451
|
# This and #instrument_name is for caching instrument
|
data/lib/active_agent/base.rb
CHANGED
@@ -28,8 +28,8 @@ module ActiveAgent
|
|
28
28
|
# It is built on top of ActionPrompt which provides methods for generating content, handling actions, and managing prompts.
|
29
29
|
# ActiveAgent::Base is designed to be extended by specific agent implementations.
|
30
30
|
# It provides a common set of agent actions for self-contained agents that can determine their own actions using all available actions.
|
31
|
-
# Base actions include:
|
32
|
-
def
|
31
|
+
# Base actions include: prompt_context, continue, reasoning, reiterate, and conclude
|
32
|
+
def prompt_context
|
33
33
|
prompt(stream: params[:stream], messages: params[:messages], message: params[:message], context_id: params[:context_id])
|
34
34
|
end
|
35
35
|
end
|
data/lib/active_agent/version.rb
CHANGED
@@ -7,10 +7,10 @@ module ActiveAgent
|
|
7
7
|
|
8
8
|
argument :actions, type: :array, default: [], banner: "method method"
|
9
9
|
|
10
|
-
check_class_collision
|
10
|
+
check_class_collision suffix: "Agent"
|
11
11
|
|
12
12
|
def create_agent_file
|
13
|
-
template "agent.rb", File.join("app/agents", class_path, "#{file_name}.rb")
|
13
|
+
template "agent.rb", File.join("app/agents", class_path, "#{file_name}_agent.rb")
|
14
14
|
|
15
15
|
in_root do
|
16
16
|
if behavior == :invoke && !File.exist?(application_agent_file_name)
|
@@ -19,10 +19,12 @@ module ActiveAgent
|
|
19
19
|
end
|
20
20
|
end
|
21
21
|
|
22
|
+
hook_for :template_engine, :test_framework
|
23
|
+
|
22
24
|
private
|
23
25
|
|
24
26
|
def file_name # :doc:
|
25
|
-
@_file_name ||= super
|
27
|
+
@_file_name ||= super.sub(/_agent\z/i, "")
|
26
28
|
end
|
27
29
|
|
28
30
|
def application_agent_file_name
|
@@ -5,18 +5,11 @@ module ActiveAgent
|
|
5
5
|
class InstallGenerator < ::Rails::Generators::Base
|
6
6
|
source_root File.expand_path("templates", __dir__)
|
7
7
|
|
8
|
+
hook_for :template_engine, :test_framework
|
9
|
+
|
8
10
|
def create_configuration
|
9
11
|
template "active_agent.yml", "config/active_agent.yml"
|
10
12
|
end
|
11
|
-
|
12
|
-
def create_application_agent
|
13
|
-
template "application_agent.rb", "app/agents/application_agent.rb"
|
14
|
-
end
|
15
|
-
|
16
|
-
def create_agent_layouts
|
17
|
-
template "agent.html.erb", "app/views/layouts/agent.html.erb"
|
18
|
-
template "agent.text.erb", "app/views/layouts/agent.text.erb"
|
19
|
-
end
|
20
13
|
end
|
21
14
|
end
|
22
15
|
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "rails/generators/erb"
|
4
|
+
|
5
|
+
module Erb # :nodoc:
|
6
|
+
module Generators # :nodoc:
|
7
|
+
class AgentGenerator < Base # :nodoc:
|
8
|
+
source_root File.expand_path("templates", __dir__)
|
9
|
+
argument :actions, type: :array, default: [], banner: "method method"
|
10
|
+
|
11
|
+
def copy_view_files
|
12
|
+
view_base_path = File.join("app/views", class_path, file_name + "_agent")
|
13
|
+
empty_directory view_base_path
|
14
|
+
|
15
|
+
if behavior == :invoke
|
16
|
+
formats.each do |format|
|
17
|
+
layout_path = File.join("app/views/layouts", class_path, filename_with_extensions("agent", format))
|
18
|
+
template filename_with_extensions(:layout, format), layout_path unless File.exist?(layout_path)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
actions.each do |action|
|
23
|
+
@action = action
|
24
|
+
|
25
|
+
formats.each do |format|
|
26
|
+
@path = File.join(view_base_path, filename_with_extensions(action, format))
|
27
|
+
template filename_with_extensions(:view, format), @path
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def formats
|
35
|
+
[ :text, :html ]
|
36
|
+
end
|
37
|
+
|
38
|
+
def file_name
|
39
|
+
@_file_name ||= super.sub(/_agent\z/i, "")
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "rails/generators"
|
4
|
+
|
5
|
+
module Erb # :nodoc:
|
6
|
+
module Generators # :nodoc:
|
7
|
+
class InstallGenerator < ::Rails::Generators::Base # :nodoc:
|
8
|
+
source_root File.expand_path("templates", __dir__)
|
9
|
+
|
10
|
+
def create_agent_layouts
|
11
|
+
template "layout.html.erb.tt", "app/views/layouts/agent.html.erb"
|
12
|
+
template "layout.text.erb.tt", "app/views/layouts/agent.text.erb"
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
<%%= yield %>
|
@@ -0,0 +1 @@
|
|
1
|
+
<%%= yield %>
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "rails/generators/test_unit"
|
4
|
+
|
5
|
+
module TestUnit # :nodoc:
|
6
|
+
module Generators # :nodoc:
|
7
|
+
class AgentGenerator < Base # :nodoc:
|
8
|
+
source_root File.expand_path("templates", __dir__)
|
9
|
+
argument :actions, type: :array, default: [], banner: "method method"
|
10
|
+
|
11
|
+
def check_class_collision
|
12
|
+
class_collisions "#{class_name}AgentTest", "#{class_name}AgentPreview"
|
13
|
+
end
|
14
|
+
|
15
|
+
def create_test_files
|
16
|
+
template "functional_test.rb", File.join("test/agents", class_path, "#{file_name}_agent_test.rb")
|
17
|
+
end
|
18
|
+
|
19
|
+
def create_preview_files
|
20
|
+
template "preview.rb", File.join("test/agents/previews", class_path, "#{file_name}_agent_preview.rb")
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def file_name
|
26
|
+
@_file_name ||= super.sub(/_agent\z/i, "")
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "rails/generators/test_unit"
|
4
|
+
|
5
|
+
module TestUnit # :nodoc:
|
6
|
+
module Generators # :nodoc:
|
7
|
+
class InstallGenerator < Base # :nodoc:
|
8
|
+
# TestUnit install generator for ActiveAgent
|
9
|
+
# This can be used to create additional test-specific files during installation
|
10
|
+
# Currently no additional files are needed for TestUnit setup
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require "test_helper"
|
2
|
+
|
3
|
+
<% module_namespacing do -%>
|
4
|
+
class <%= class_name %>AgentTest < ActiveAgent::TestCase
|
5
|
+
<% actions.each_with_index do |action, index| -%>
|
6
|
+
<% if index != 0 -%>
|
7
|
+
|
8
|
+
<% end -%>
|
9
|
+
test "<%= action %>" do
|
10
|
+
agent = <%= class_name %>Agent.<%= action %>
|
11
|
+
assert_equal <%= action.to_s.humanize.inspect %>, agent.prompt_context
|
12
|
+
end
|
13
|
+
<% end -%>
|
14
|
+
<% if actions.blank? -%>
|
15
|
+
# test "the truth" do
|
16
|
+
# assert true
|
17
|
+
# end
|
18
|
+
<% end -%>
|
19
|
+
end
|
20
|
+
<% end -%>
|
@@ -0,0 +1,14 @@
|
|
1
|
+
<% module_namespacing do -%>
|
2
|
+
# Preview all agent views/prompts templates at http://localhost:3000/active_agent/agents/<%= file_path %>_agent
|
3
|
+
class <%= class_name %>AgentPreview < ActiveAgent::Preview
|
4
|
+
<% actions.each_with_index do |action, index| -%>
|
5
|
+
<% if index != 0 -%>
|
6
|
+
|
7
|
+
<% end -%>
|
8
|
+
# Preview this email at http://localhost:3000/active_agent/agents/<%= file_path %>_agent/<%= action %>
|
9
|
+
def <%= action %>
|
10
|
+
<%= class_name %>Agent.<%= action %>
|
11
|
+
end
|
12
|
+
<% end -%>
|
13
|
+
end
|
14
|
+
<% end -%>
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: activeagent
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.4.
|
4
|
+
version: 0.4.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Justin Bowen
|
@@ -187,15 +187,20 @@ files:
|
|
187
187
|
- lib/generators/active_agent/USAGE
|
188
188
|
- lib/generators/active_agent/agent_generator.rb
|
189
189
|
- lib/generators/active_agent/install_generator.rb
|
190
|
-
- lib/generators/active_agent/templates/action.html.erb.tt
|
191
|
-
- lib/generators/active_agent/templates/action.json.jbuilder.tt
|
192
190
|
- lib/generators/active_agent/templates/active_agent.yml
|
193
|
-
- lib/generators/active_agent/templates/agent.html.erb
|
194
191
|
- lib/generators/active_agent/templates/agent.rb.tt
|
195
|
-
- lib/generators/active_agent/templates/agent.text.erb
|
196
|
-
- lib/generators/active_agent/templates/agent_spec.rb.tt
|
197
|
-
- lib/generators/active_agent/templates/agent_test.rb.tt
|
198
192
|
- lib/generators/active_agent/templates/application_agent.rb.tt
|
193
|
+
- lib/generators/erb/agent_generator.rb
|
194
|
+
- lib/generators/erb/install_generator.rb
|
195
|
+
- lib/generators/erb/templates/application_agent.rb.tt
|
196
|
+
- lib/generators/erb/templates/layout.html.erb.tt
|
197
|
+
- lib/generators/erb/templates/layout.text.erb.tt
|
198
|
+
- lib/generators/erb/templates/view.html.erb.tt
|
199
|
+
- lib/generators/erb/templates/view.text.erb.tt
|
200
|
+
- lib/generators/test_unit/agent_generator.rb
|
201
|
+
- lib/generators/test_unit/install_generator.rb
|
202
|
+
- lib/generators/test_unit/templates/functional_test.rb.tt
|
203
|
+
- lib/generators/test_unit/templates/preview.rb.tt
|
199
204
|
- lib/tasks/activeagent_tasks.rake
|
200
205
|
homepage: https://activeagents.ai
|
201
206
|
licenses:
|
File without changes
|
@@ -1,14 +0,0 @@
|
|
1
|
-
json.type :function
|
2
|
-
json.function do
|
3
|
-
json.name action_name
|
4
|
-
json.description "TODO: Write a description for this action"
|
5
|
-
json.parameters do
|
6
|
-
json.type :object
|
7
|
-
json.properties do
|
8
|
-
json.param_name do
|
9
|
-
json.type :string
|
10
|
-
json.description "The param_description"
|
11
|
-
end
|
12
|
-
end
|
13
|
-
end
|
14
|
-
end
|
@@ -1 +0,0 @@
|
|
1
|
-
<%= yield if block_given? %>
|
@@ -1 +0,0 @@
|
|
1
|
-
<%= yield if block_given? %>
|
File without changes
|
File without changes
|