pwn 0.5.752 → 0.5.754

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: 9f090409a234eeffe46292971f044bd3f6109da51d03e8fae5909d63aa625808
4
- data.tar.gz: '082fcd50fde34da2c0bea93a7292a4925fd017e2526e07d6fcf8c9b29bb5db8c'
3
+ metadata.gz: e01b8e88574a241d780acc3e2c4d70a7e5c07fb1e9e17fa709363a4bfcbc7898
4
+ data.tar.gz: f50a1f5f35d25f300c396678edb5daa8119f4fca9e1567d71652c7f45bcc1dcb
5
5
  SHA512:
6
- metadata.gz: 612c4b3a4d8f8311e231c2bbcceee5cd61280134157adc9f2132beb438ef65bbb698ed949aca56d19412414ed10c8540a1feab8327aaf14abcc71c33c2b23ccf
7
- data.tar.gz: '09b64fed3ccd1e064a28b4874005e73549c8fbd7d60017dd6c552cce7bd6b45577dfb9202d7aecc54aa13456a2cdc847ef021850d387139bfa064fdc88fbc7d5'
6
+ metadata.gz: c34deaa7b6835b37cacb76a2863cb0759b4dc727301692014702468aa3d4a04ddc1c76fda3893005f58071b9060768ade05401dbbae1cac2fc3dbfc99bcfa9f0
7
+ data.tar.gz: de481a34a2b3776fa8003094618af168f6a33341fd236e14daa3247694e8e703e0eddb15ea74556e9bd4814a14a3187d48f51c18be90094f1bd4c83ddcd364b8
data/Gemfile CHANGED
@@ -20,7 +20,7 @@ gem 'base32', '0.3.4'
20
20
  gem 'bitcoin-ruby', '0.0.20'
21
21
  gem 'brakeman', '8.0.6'
22
22
  gem 'bson', '5.2.0'
23
- gem 'bundler', '>=4.0.21'
23
+ gem 'bundler', '>=4.0.22'
24
24
  gem 'bundler-audit', '>=0.9.3'
25
25
  gem 'bunny', '3.4.0'
26
26
  gem 'colorize', '1.1.0'
@@ -57,7 +57,7 @@ gem 'msfrpc-client', '1.1.2'
57
57
  gem 'netaddr', '2.0.6'
58
58
  gem 'net-ldap', '0.20.0'
59
59
  gem 'net-openvpn', '0.8.7'
60
- gem 'net-smtp', '0.5.1'
60
+ gem 'net-smtp', '0.5.2'
61
61
  gem 'nexpose', '7.3.0'
62
62
  gem 'nokogiri', '1.19.4'
63
63
  gem 'nokogiri-diff', '0.3.0'
@@ -575,7 +575,7 @@ module PWN
575
575
  {
576
576
  name: fn[:name] || fn['name'],
577
577
  description: fn[:description] || fn['description'],
578
- input_schema: fn[:parameters] || fn['parameters'] || { type: 'object', properties: {} }
578
+ input_schema: anthropic_input_schema(schema: fn[:parameters] || fn['parameters'])
579
579
  }
580
580
  end
581
581
  http_body[:tool_choice] = anth_tool_choice(choice: opts[:tool_choice]) if opts[:tool_choice]
@@ -598,6 +598,78 @@ module PWN
598
598
  raise e
599
599
  end
600
600
 
601
+ # Anthropic rejects oneOf, allOf, and anyOf at the root of input_schema.
602
+ # Return a deep copy with those root keys removed and branch properties merged.
603
+ # Nested combinators and the caller's schema stay unchanged.
604
+ private_class_method def self.anthropic_input_schema(opts = {})
605
+ schema = opts[:schema]
606
+ return { type: 'object', properties: {} } unless schema.is_a?(Hash)
607
+
608
+ copy = json_copy(value: schema)
609
+ notes = []
610
+ %w[anyOf oneOf allOf].each do |name|
611
+ key = anthropic_schema_key(schema: copy, name: name)
612
+ next unless key
613
+
614
+ Array(copy.delete(key)).each do |branch|
615
+ next unless branch.is_a?(Hash)
616
+
617
+ anthropic_schema_notes(branch: branch, notes: notes)
618
+ props = branch[:properties] || branch['properties']
619
+ next unless props.is_a?(Hash)
620
+
621
+ dest = copy.key?('properties') ? 'properties' : :properties
622
+ copy[dest] = {} unless copy[dest].is_a?(Hash)
623
+ props.each { |prop, spec| copy[dest][prop] = spec unless copy[dest].key?(prop) }
624
+ end
625
+ end
626
+ copy[:type] = 'object' unless copy.key?(:type) || copy.key?('type')
627
+ copy[:properties] = {} unless copy.key?(:properties) || copy.key?('properties')
628
+ unless notes.empty?
629
+ note = "One of these field sets is required: #{notes.uniq.join('; ')}."
630
+ copy[:description] = [copy[:description] || copy['description'], note].compact.reject { |part| part.to_s.empty? }.join(' ')
631
+ copy.delete('description')
632
+ end
633
+ copy
634
+ end
635
+
636
+ private_class_method def self.anthropic_schema_notes(opts = {})
637
+ branch = opts[:branch]
638
+ notes = opts[:notes]
639
+ return notes unless branch.is_a?(Hash) && notes.is_a?(Array)
640
+
641
+ required = branch[:required] || branch['required']
642
+ notes << required.join('+') if required.is_a?(Array) && required.any?
643
+ %w[anyOf oneOf allOf].each do |name|
644
+ key = anthropic_schema_key(schema: branch, name: name)
645
+ next unless key
646
+
647
+ Array(branch[key]).each { |child| anthropic_schema_notes(branch: child, notes: notes) }
648
+ end
649
+ notes
650
+ end
651
+
652
+ private_class_method def self.anthropic_schema_key(opts = {})
653
+ schema = opts[:schema]
654
+ name = opts[:name]
655
+ return name if schema.is_a?(Hash) && schema.key?(name)
656
+ return name.to_sym if schema.is_a?(Hash) && schema.key?(name.to_sym)
657
+
658
+ nil
659
+ end
660
+
661
+ private_class_method def self.json_copy(opts = {})
662
+ value = opts[:value]
663
+ case value
664
+ when Hash
665
+ value.each_with_object({}) { |(key, item), out| out[key] = json_copy(value: item) }
666
+ when Array
667
+ value.map { |item| json_copy(value: item) }
668
+ else
669
+ value.is_a?(String) ? value.dup : value
670
+ end
671
+ end
672
+
601
673
  # OpenAI messages[] -> [system_string, anthropic messages[]]
602
674
  private_class_method def self.oa_messages_to_anthropic(opts = {})
603
675
  messages = opts[:messages] ||= []
@@ -1070,10 +1070,10 @@ module PWN
1070
1070
  role = cfg[:system_role_content].to_s.gsub(/\s+/, ' ').strip
1071
1071
  parts = []
1072
1072
  parts << "SYSTEM ROLE CONTENT: #{role}" unless role.empty?
1073
- parts << "\nTEMP: #{cfg[:temp]}" unless cfg[:temp].nil?
1074
- parts << " MAX TOKENS: #{cfg[:max_tokens]}" unless cfg[:max_tokens].nil?
1073
+ parts << "\nMAX TOKENS: #{cfg[:max_tokens]}" unless cfg[:max_tokens].nil?
1075
1074
  parts << " MAX PROMPT LENGTH: #{cfg[:max_prompt_length]}" unless cfg[:max_prompt_length].nil?
1076
- parts << "\nREASONING EFFORT: #{cfg[:reasoning_effort]}" unless cfg[:reasoning_effort].to_s.empty?
1075
+ parts << " REASONING EFFORT: #{cfg[:reasoning_effort]}" unless cfg[:reasoning_effort].to_s.empty?
1076
+ parts << " TEMP: #{cfg[:temp]}" unless cfg[:temp].nil?
1077
1077
  parts.join(' · ')
1078
1078
  rescue StandardError
1079
1079
  ''
data/lib/pwn/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module PWN
4
- VERSION = '0.5.752'
4
+ VERSION = '0.5.754'
5
5
  end
@@ -58,4 +58,52 @@ describe PWN::AI::Anthropic do
58
58
  expect(src).to include('PromptCache.enabled?')
59
59
  end
60
60
  end
61
+
62
+ it 'drops root combinators from Anthropic input_schema without mutating registry schemas' do
63
+ allow(PWN::Env).to receive(:[]).with(:ai).and_return(
64
+ anthropic: { key: 'test-key', model: 'claude-test', temp: 1, max_tokens: 100 }
65
+ )
66
+ allow(PWN::AI::Agent::PromptCache).to receive(:enabled?).and_return(false) if defined?(PWN::AI::Agent::PromptCache)
67
+ tools = PWN::AI::Agent::Registry.definitions(core_only: true)
68
+ artifact = tools.find { |tool| tool.dig(:function, :name) == 'artifact_read' }
69
+ original = artifact[:function][:parameters]
70
+ nested = {
71
+ type: 'object',
72
+ properties: {
73
+ item: { anyOf: [{ type: 'string' }, { type: 'object', properties: { handle: { type: 'string' } } }] }
74
+ }
75
+ }
76
+ union = {
77
+ 'anyOf' => [
78
+ { 'type' => 'object', 'properties' => { 'handle' => { 'type' => 'string' } }, 'required' => ['handle'] },
79
+ { 'type' => 'object', 'properties' => { 'path' => { 'type' => 'string' } }, 'required' => ['path'] }
80
+ ]
81
+ }
82
+ sent = tools + [
83
+ { type: 'function', function: { name: 'nested_union', description: 'keep nested', parameters: nested } },
84
+ { type: 'function', function: { name: 'root_union', description: 'flatten', parameters: union } }
85
+ ]
86
+ captured = nil
87
+ allow(described_class).to receive(:anthropic_rest_call) do |opts|
88
+ captured = opts[:http_body]
89
+ '{"id":"msg_schema","type":"message","role":"assistant","content":[{"type":"text","text":"ok"}],"stop_reason":"end_turn"}'
90
+ end
91
+
92
+ described_class.chat_with_tools(messages: [{ role: 'user', content: 'hello' }], tools: sent, model: 'claude-test')
93
+
94
+ expect(original).to have_key(:anyOf)
95
+ wire = captured[:tools]
96
+ expect(wire.map { |tool| tool[:name] }[10]).to eq('artifact_read')
97
+ wire.each do |tool|
98
+ schema = tool[:input_schema]
99
+ expect(schema.keys.map(&:to_s)).not_to include('anyOf', 'oneOf', 'allOf')
100
+ end
101
+ read_schema = wire[10][:input_schema]
102
+ expect(read_schema[:properties].keys.map(&:to_s)).to include('handle', 'path', 'ref')
103
+ expect(read_schema[:description]).to include('handle', 'path', 'ref')
104
+ expect(wire.find { |tool| tool[:name] == 'nested_union' }[:input_schema][:properties][:item]).to have_key(:anyOf)
105
+ flat = wire.find { |tool| tool[:name] == 'root_union' }[:input_schema]
106
+ expect(flat[:properties].keys.map(&:to_s)).to include('handle', 'path')
107
+ expect(flat[:description]).to include('handle', 'path')
108
+ end
61
109
  end
@@ -535,10 +535,10 @@ describe 'pwn-ai curses launch' do # rubocop:disable Metrics/BlockLength -- publ
535
535
  expect(console.instance_variable_get(:@header_text_column)).to eq(2)
536
536
  end
537
537
  allow(curses).to receive_messages(cols: 120, lines: 26)
538
- PWN::Env[:ai][:ollama][:system_role_content] = 'operator ' * 110
538
+ PWN::Env[:ai][:ollama][:system_role_content] = 'operator ' * 200
539
539
  console.draw
540
540
  expect(console.instance_variable_get(:@header_text_column)).to eq(2)
541
- expect(console.header_lines('ollama', 'fixture', 115).length).to be <= 13
541
+ expect(console.header_lines('ollama', 'fixture', 115).length).to be > 16
542
542
  expect(PWN::Banner).to have_received(:mini_cells).once
543
543
  expect([console.instance_variable_get(:@editor).text, console.instance_variable_get(:@editor).cursor]).to eq(['retained draft', 4])
544
544
  end
@@ -719,13 +719,15 @@ describe 'pwn-ai curses launch' do # rubocop:disable Metrics/BlockLength -- publ
719
719
  expect(actual).to eq(lines)
720
720
  expect(header.select { |entry| entry[3] == console.tone(:category) }.map { |entry| entry[2] }.join).to include('SYSTEM ROLE CONTENT:', 'MAX PROMPT LENGTH:', 'REASONING EFFORT:')
721
721
  expect(header.select { |entry| entry[3] == console.tone(:header) }.map { |entry| entry[2] }.join).to include('operator', '1024', 'high')
722
- if columns == 48
723
- labels = header.select { |entry| entry[3] == console.tone(:category) }.map { |entry| entry[2] }
724
- expect(labels.join).to include('MAX PROMPT LENGTH:')
725
- expect(labels).not_to include(a_string_including('MAX PROMPT LENGTH:'))
726
- end
727
722
  expect(console.instance_variable_get(:@page_size)).to be >= 1
728
723
  end
724
+ console.header_lines('ollama', 'fixture', 35)
725
+ labels = []
726
+ console.instance_variable_get(:@header_spans).each do |row|
727
+ row.each { |role, text| labels << text if role == :category }
728
+ end
729
+ expect(labels.join).to include('MAX PROMPT LENGTH:')
730
+ expect(labels).not_to include(a_string_including('MAX PROMPT LENGTH:'))
729
731
  console.handle("\u000f")
730
732
  console.draw
731
733
  console.handle(:end)
@@ -1238,7 +1240,9 @@ describe 'pwn-ai curses launch' do # rubocop:disable Metrics/BlockLength -- publ
1238
1240
  if rendered_header.include?('worker-effort')
1239
1241
  expect(rendered_header).to include('worker settings role', '0.42', '4321', '8765')
1240
1242
  expect(painted).to include([a_kind_of(Integer), a_kind_of(Integer), 'MODEL:', 7])
1241
- expect(painted).to include([a_kind_of(Integer), a_kind_of(Integer), ' worker-effort', 6])
1243
+ value = painted.find { |_y, _x, text, color| color == 6 && text.to_s.include?('worker-effort') }
1244
+ expect(value).not_to be_nil
1245
+ expect(value[2]).not_to include('REASONING EFFORT:')
1242
1246
  "\u0004"
1243
1247
  else
1244
1248
  Thread.pass
@@ -1058,13 +1058,17 @@
1058
1058
  {"messages":[{"role":"user","content":"PWN::AI::Agent::VulnGen.authors Usage"},{"role":"assistant","content":"`PWN::AI::Agent::VulnGen.authors`: Author(s)\n\n0day Inc. <support@0dayinc.com>\n"}]}
1059
1059
  {"messages":[{"role":"user","content":"PWN::AI::Agent::VulnGen.help Usage"},{"role":"assistant","content":"`PWN::AI::Agent::VulnGen.help`: "}]}
1060
1060
  {"messages":[{"role":"user","content":"PWN::AI::Anthropic.anth_tool_choice Usage"},{"role":"assistant","content":"`PWN::AI::Anthropic.anth_tool_choice`: "}]}
1061
+ {"messages":[{"role":"user","content":"PWN::AI::Anthropic.anthropic_input_schema Usage"},{"role":"assistant","content":"`PWN::AI::Anthropic.anthropic_input_schema`: "}]}
1061
1062
  {"messages":[{"role":"user","content":"PWN::AI::Anthropic.anthropic_resp_to_oa Usage"},{"role":"assistant","content":"`PWN::AI::Anthropic.anthropic_resp_to_oa`: "}]}
1062
1063
  {"messages":[{"role":"user","content":"PWN::AI::Anthropic.anthropic_rest_call Usage"},{"role":"assistant","content":"`PWN::AI::Anthropic.anthropic_rest_call`: Supported Method Parameters\n\nanthropic_rest_call(\n\ntoken: 'required - anthropic api key',\nhttp_method: 'optional HTTP method (defaults to GET)',\nbase_uri: 'optional base anthropic api URI (defaults to https://api.anthropic.com/v1)',\nrest_call: 'required rest call to make per the schema',\nparams: 'optional params passed in the URI or HTTP Headers',\nhttp_body: 'optional HTTP body sent in HTTP methods that support it e.g. POST',\ntimeout: 'optional timeout in seconds (defaults to 900)',\nspinner: 'optional - display spinner (defaults to false)'\n\n)\n"}]}
1064
+ {"messages":[{"role":"user","content":"PWN::AI::Anthropic.anthropic_schema_key Usage"},{"role":"assistant","content":"`PWN::AI::Anthropic.anthropic_schema_key`: "}]}
1065
+ {"messages":[{"role":"user","content":"PWN::AI::Anthropic.anthropic_schema_notes Usage"},{"role":"assistant","content":"`PWN::AI::Anthropic.anthropic_schema_notes`: "}]}
1063
1066
  {"messages":[{"role":"user","content":"PWN::AI::Anthropic.authors Usage"},{"role":"assistant","content":"`PWN::AI::Anthropic.authors`: Author(s)\n\n0day Inc. <support@0dayinc.com>\n"}]}
1064
1067
  {"messages":[{"role":"user","content":"PWN::AI::Anthropic.chat Usage"},{"role":"assistant","content":"`PWN::AI::Anthropic.chat`: Supported Method Parameters\n\nresponse = PWN::AI::Anthropic.chat(\n\nrequest: 'required - message to Anthropic',\nmodel: 'optional - model to use for text generation (defaults to PWN::Env[:ai][:anthropic][:model])',\ntemp: 'optional - creative response float (defaults to PWN::Env[:ai][:anthropic][:temp])',\nsystem_role_content: 'optional - context to set up the model behavior for conversation (Default: PWN::Env[:ai][:anthropic][:system_role_content])',\nresponse_history: 'optional - pass response back in to have a conversation',\nspeak_answer: 'optional speak answer using PWN::Plugins::Voice.text_to_speech (Default: nil)',\ntimeout: 'optional timeout in seconds (defaults to 900)',\nspinner: 'optional - display spinner (defaults to false)'\n\n)\n"}]}
1065
1068
  {"messages":[{"role":"user","content":"PWN::AI::Anthropic.chat_with_tools Usage"},{"role":"assistant","content":"`PWN::AI::Anthropic.chat_with_tools`: Supported Method Parameters\n\nresponse = PWN::AI::Anthropic.chat_with_tools(\n\nmessages: 'required - OpenAI-format messages array (system/user/assistant/tool)',\ntools: 'optional - OpenAI tools array [{type:\"function\", function:{...}}]',\ntool_choice: 'optional - \"auto\" | \"none\" | \"required\" | {type:\"function\", function:{name:..}}',\nmodel: 'optional - overrides PWN::Env[:ai][:anthropic][:model]',\ntemp: 'optional - temperature (defaults to PWN::Env[:ai][:anthropic][:temp] || 1)',\nmax_tokens: 'optional - defaults to PWN::Env[:ai][:anthropic][:max_tokens] || 128_000',\ntimeout: 'optional - seconds (default 900)',\nspinner: 'optional - display spinner (default false)'\n\n)\n"}]}
1066
1069
  {"messages":[{"role":"user","content":"PWN::AI::Anthropic.get_models Usage"},{"role":"assistant","content":"`PWN::AI::Anthropic.get_models`: Supported Method Parameters\n\nmodels = PWN::AI::Anthropic.get_models\n"}]}
1067
1070
  {"messages":[{"role":"user","content":"PWN::AI::Anthropic.help Usage"},{"role":"assistant","content":"`PWN::AI::Anthropic.help`: "}]}
1071
+ {"messages":[{"role":"user","content":"PWN::AI::Anthropic.json_copy Usage"},{"role":"assistant","content":"`PWN::AI::Anthropic.json_copy`: "}]}
1068
1072
  {"messages":[{"role":"user","content":"PWN::AI::Anthropic.jwt_exp Usage"},{"role":"assistant","content":"`PWN::AI::Anthropic.jwt_exp`: "}]}
1069
1073
  {"messages":[{"role":"user","content":"PWN::AI::Anthropic.oa_messages_to_anthropic Usage"},{"role":"assistant","content":"`PWN::AI::Anthropic.oa_messages_to_anthropic`: "}]}
1070
1074
  {"messages":[{"role":"user","content":"PWN::AI::Anthropic.oauth_token_expiring? Usage"},{"role":"assistant","content":"`PWN::AI::Anthropic.oauth_token_expiring?`: "}]}
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pwn
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.752
4
+ version: 0.5.754
5
5
  platform: ruby
6
6
  authors:
7
7
  - 0day Inc.
@@ -141,14 +141,14 @@ dependencies:
141
141
  requirements:
142
142
  - - ">="
143
143
  - !ruby/object:Gem::Version
144
- version: 4.0.21
144
+ version: 4.0.22
145
145
  type: :development
146
146
  prerelease: false
147
147
  version_requirements: !ruby/object:Gem::Requirement
148
148
  requirements:
149
149
  - - ">="
150
150
  - !ruby/object:Gem::Version
151
- version: 4.0.21
151
+ version: 4.0.22
152
152
  - !ruby/object:Gem::Dependency
153
153
  name: bundler-audit
154
154
  requirement: !ruby/object:Gem::Requirement
@@ -617,14 +617,14 @@ dependencies:
617
617
  requirements:
618
618
  - - '='
619
619
  - !ruby/object:Gem::Version
620
- version: 0.5.1
620
+ version: 0.5.2
621
621
  type: :runtime
622
622
  prerelease: false
623
623
  version_requirements: !ruby/object:Gem::Requirement
624
624
  requirements:
625
625
  - - '='
626
626
  - !ruby/object:Gem::Version
627
- version: 0.5.1
627
+ version: 0.5.2
628
628
  - !ruby/object:Gem::Dependency
629
629
  name: nexpose
630
630
  requirement: !ruby/object:Gem::Requirement
@@ -7228,7 +7228,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
7228
7228
  - !ruby/object:Gem::Version
7229
7229
  version: '0'
7230
7230
  requirements: []
7231
- rubygems_version: 4.0.21
7231
+ rubygems_version: 4.0.22
7232
7232
  specification_version: 4
7233
7233
  summary: Automated Security Testing for CI/CD Pipelines & Beyond
7234
7234
  test_files: []