pwn 0.5.606 → 0.5.612
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/Gemfile +5 -5
- data/README.md +3 -3
- data/git_commit.sh +9 -2
- data/lib/pwn/ai/agent/loop.rb +120 -99
- data/lib/pwn/ai/agent/prompt_builder.rb +3 -4
- data/lib/pwn/ai/agent/tools/ruby_eval.rb +16 -2
- data/lib/pwn/ai/anthropic.rb +8 -8
- data/lib/pwn/ai/gemini.rb +10 -10
- data/lib/pwn/ai/grok.rb +137 -19
- data/lib/pwn/ai/introspection.rb +8 -50
- data/lib/pwn/ai/ollama.rb +7 -7
- data/lib/pwn/ai/open_ai.rb +31 -31
- data/lib/pwn/plugins/repl.rb +3 -3
- data/lib/pwn/plugins/zaproxy.rb +4 -2
- data/lib/pwn/plugins/zaproxy.rb.bak +837 -0
- data/lib/pwn/version.rb +1 -1
- metadata +12 -11
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 8beaa60314e788a7122a92098c1ea4897cf488ef88efd1030f1f704656dbe1ff
|
|
4
|
+
data.tar.gz: 94fff44f3cdc34c997fb920f63affa133cab0de4d309acf4c09f4451adbbdcf6
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d9869f0a43c78a53238b1ef1ab58f54f27f1da575ad101aff4c172e813ec7878b92465f5d0f8984bce33a1a6227ec3a67c80ee63e1643683deaa45e2860e9c52
|
|
7
|
+
data.tar.gz: 1407422fb0cc44c84232f37f664a07eb0a80bafaf748e8aadbe310652640ed83526e1ce48d34a1d0302d4537c27ca71983c44595824b39080693157ba038322f
|
data/Gemfile
CHANGED
|
@@ -50,7 +50,7 @@ gem 'libusb', '0.7.2'
|
|
|
50
50
|
gem 'luhn', '3.0.0'
|
|
51
51
|
gem 'mail', '2.9.0'
|
|
52
52
|
gem 'mcp', '0.20.0'
|
|
53
|
-
gem 'meshtastic', '0.0.
|
|
53
|
+
gem 'meshtastic', '0.0.165'
|
|
54
54
|
gem 'metasm', '1.0.6'
|
|
55
55
|
gem 'mongo', '2.24.1'
|
|
56
56
|
gem 'msfrpc-client', '1.1.2'
|
|
@@ -77,11 +77,11 @@ gem 'rbvmomi2', '3.10.0'
|
|
|
77
77
|
gem 'rdoc', '7.0.4'
|
|
78
78
|
gem 'rest-client', '2.1.0'
|
|
79
79
|
gem 'rex', '2.0.13'
|
|
80
|
-
gem 'rmagick', '7.0.
|
|
80
|
+
gem 'rmagick', '7.0.4'
|
|
81
81
|
gem 'rqrcode', '3.2.0'
|
|
82
82
|
gem 'rspec', '3.13.2'
|
|
83
83
|
gem 'rtesseract', '3.1.4'
|
|
84
|
-
gem 'rubocop', '1.
|
|
84
|
+
gem 'rubocop', '1.88.0'
|
|
85
85
|
gem 'rubocop-rake', '0.7.1'
|
|
86
86
|
gem 'rubocop-rspec', '3.10.2'
|
|
87
87
|
gem 'ruby-audio', '1.6.1'
|
|
@@ -89,8 +89,8 @@ gem 'ruby-nmap', '1.0.3'
|
|
|
89
89
|
gem 'ruby-saml', '1.18.1'
|
|
90
90
|
gem 'rvm', '1.11.3.9'
|
|
91
91
|
gem 'savon', '2.17.2'
|
|
92
|
-
gem 'selenium-devtools', '0.
|
|
93
|
-
gem 'selenium-webdriver', '4.
|
|
92
|
+
gem 'selenium-devtools', '0.149.0'
|
|
93
|
+
gem 'selenium-webdriver', '4.45.0'
|
|
94
94
|
gem 'slack-ruby-client', '3.1.0'
|
|
95
95
|
gem 'socksify', '1.8.1'
|
|
96
96
|
gem 'spreadsheet', '1.3.5'
|
data/README.md
CHANGED
|
@@ -37,7 +37,7 @@ $ cd /opt/pwn
|
|
|
37
37
|
$ ./install.sh
|
|
38
38
|
$ ./install.sh ruby-gem
|
|
39
39
|
$ pwn
|
|
40
|
-
pwn[v0.5.
|
|
40
|
+
pwn[v0.5.612]:001 >>> PWN.help
|
|
41
41
|
```
|
|
42
42
|
|
|
43
43
|
[](https://youtu.be/G7iLUY4FzsI)
|
|
@@ -52,7 +52,7 @@ $ rvm use ruby-4.0.5@pwn
|
|
|
52
52
|
$ gem uninstall --all --executables pwn
|
|
53
53
|
$ gem install --verbose pwn
|
|
54
54
|
$ pwn
|
|
55
|
-
pwn[v0.5.
|
|
55
|
+
pwn[v0.5.612]:001 >>> PWN.help
|
|
56
56
|
```
|
|
57
57
|
|
|
58
58
|
If you're using a multi-user install of RVM do:
|
|
@@ -62,7 +62,7 @@ $ rvm use ruby-4.0.5@pwn
|
|
|
62
62
|
$ rvmsudo gem uninstall --all --executables pwn
|
|
63
63
|
$ rvmsudo gem install --verbose pwn
|
|
64
64
|
$ pwn
|
|
65
|
-
pwn[v0.5.
|
|
65
|
+
pwn[v0.5.612]:001 >>> PWN.help
|
|
66
66
|
```
|
|
67
67
|
|
|
68
68
|
PWN periodically upgrades to the latest version of Ruby which is reflected in `/opt/pwn/.ruby-version`. The easiest way to upgrade to the latest version of Ruby from a previous PWN installation is to run the following script:
|
data/git_commit.sh
CHANGED
|
@@ -25,8 +25,15 @@ if (( $# == 3 )); then
|
|
|
25
25
|
|
|
26
26
|
pwn_autoinc_version
|
|
27
27
|
if [[ $? -ne 0 ]]; then
|
|
28
|
-
echo 'ERROR: pwn_autoinc_version failed!
|
|
29
|
-
|
|
28
|
+
echo 'ERROR: pwn_autoinc_version failed! Reinstalling pwn gemset...'
|
|
29
|
+
rvmsudo ./reinstall_pwn_gemset.sh
|
|
30
|
+
rvmsudo rake
|
|
31
|
+
rvmsudo rake install
|
|
32
|
+
if [[ $? -ne 0 ]]; then
|
|
33
|
+
echo 'ERROR: Attempt to reinstall pwn gemset failed! Please investigate and fix before trying again.'
|
|
34
|
+
exit 1
|
|
35
|
+
fi
|
|
36
|
+
pwn_autoinc_version
|
|
30
37
|
fi
|
|
31
38
|
|
|
32
39
|
# Generate RDoc JSONL for fine-tunning LLMs
|
data/lib/pwn/ai/agent/loop.rb
CHANGED
|
@@ -15,7 +15,7 @@ module PWN
|
|
|
15
15
|
# externalised — Loop.run is stateless aside from the messages array it
|
|
16
16
|
# builds.
|
|
17
17
|
module Loop
|
|
18
|
-
DEFAULT_MAX_ITERS =
|
|
18
|
+
DEFAULT_MAX_ITERS = 777
|
|
19
19
|
|
|
20
20
|
ENGINE_MODS = {
|
|
21
21
|
openai: 'PWN::AI::OpenAI',
|
|
@@ -25,60 +25,74 @@ module PWN
|
|
|
25
25
|
gemini: 'PWN::AI::Gemini'
|
|
26
26
|
}.freeze
|
|
27
27
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
# session_id: 'optional - PWN::Sessions id (transcript is appended to it)',
|
|
32
|
-
# enabled_toolsets: 'optional - subset of Registry.toolsets, or nil for all',
|
|
33
|
-
# on_tool: 'optional - ->(name, args, result) callback for live UI'
|
|
34
|
-
# )
|
|
28
|
+
private_class_method def self.degrade_text_only(opts = {})
|
|
29
|
+
mod = opts[:mod]
|
|
30
|
+
messages = opts[:messages]
|
|
35
31
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
32
|
+
warn "[pwn-ai] #{mod} has no chat — falling back to text-only (no tool-calling)"
|
|
33
|
+
sys = messages.find { |m| m[:role] == 'system' }
|
|
34
|
+
user = messages.rfind { |m| m[:role] == 'user' }
|
|
35
|
+
r = mod.chat(
|
|
36
|
+
request: user[:content],
|
|
37
|
+
system_role_content: sys&.[](:content),
|
|
38
|
+
spinner: true
|
|
39
|
+
)
|
|
40
40
|
|
|
41
|
-
|
|
41
|
+
txt = r.is_a?(Hash) ? (r.dig(:choices, -1, :content) || r.dig(:choices, -1, :text)).to_s : r.to_s
|
|
42
|
+
{ role: 'assistant', content: txt, tool_calls: [] }
|
|
43
|
+
end
|
|
42
44
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
45
|
+
private_class_method def self.max_iters
|
|
46
|
+
v = (PWN::Env.dig(:ai, :agent, :max_iters) if defined?(PWN::Env))
|
|
47
|
+
v.to_i.positive? ? v.to_i : DEFAULT_MAX_ITERS
|
|
48
|
+
rescue StandardError
|
|
49
|
+
DEFAULT_MAX_ITERS
|
|
50
|
+
end
|
|
49
51
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
52
|
+
private_class_method def self.append_session(opts = {})
|
|
53
|
+
session_id = opts[:session_id]
|
|
54
|
+
return unless session_id && defined?(PWN::Sessions)
|
|
53
55
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
+
PWN::Sessions.append(
|
|
57
|
+
session_id: session_id,
|
|
58
|
+
role: opts[:role],
|
|
59
|
+
content: opts[:content]
|
|
60
|
+
)
|
|
61
|
+
rescue StandardError
|
|
62
|
+
nil
|
|
63
|
+
end
|
|
56
64
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
end
|
|
65
|
+
# Supported Method Parameters::
|
|
66
|
+
# msg = PWN::AI::Agent::Loop.normalize_llm(
|
|
67
|
+
# response: 'required - chat_with_tools response Hash from any provider'
|
|
68
|
+
# )
|
|
62
69
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
raw = Dispatch.call(tool_call: tc)
|
|
67
|
-
result = Result.condition(content: raw, entry: entry)
|
|
70
|
+
private_class_method def self.normalize_llm(opts = {})
|
|
71
|
+
resp = opts[:response]
|
|
72
|
+
return nil unless resp.is_a?(Hash)
|
|
68
73
|
|
|
69
|
-
|
|
74
|
+
msg = resp.dig(:choices, 0, :message) || resp[:assistant_message]
|
|
75
|
+
return nil unless msg
|
|
70
76
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
77
|
+
out = {
|
|
78
|
+
role: 'assistant',
|
|
79
|
+
content: msg[:content],
|
|
80
|
+
tool_calls: Array(msg[:tool_calls]).map do |tc|
|
|
81
|
+
{
|
|
82
|
+
id: tc[:id],
|
|
83
|
+
type: 'function',
|
|
84
|
+
function: {
|
|
85
|
+
name: tc.dig(:function, :name) || tc[:name],
|
|
86
|
+
arguments: tc.dig(:function, :arguments) || tc[:arguments]
|
|
87
|
+
}
|
|
76
88
|
}
|
|
77
|
-
append_session(session_id: session_id, role: 'tool', content: "#{name} → #{result[0, 400]}")
|
|
78
89
|
end
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
90
|
+
}
|
|
91
|
+
# Preserve provider-native content blocks so chat can round-trip
|
|
92
|
+
# them exactly on the next iteration (e.g. Anthropic requires the
|
|
93
|
+
# original tool_use block to precede a tool_result).
|
|
94
|
+
out[:_native_content] = msg[:_native_content] if msg[:_native_content]
|
|
95
|
+
out
|
|
82
96
|
end
|
|
83
97
|
|
|
84
98
|
# Supported Method Parameters::
|
|
@@ -92,9 +106,9 @@ module PWN
|
|
|
92
106
|
# tool_calls: [ {id:, type:'function', function:{name:, arguments:}} ],
|
|
93
107
|
# _native_content: <provider raw> (when adapter needs round-trip) }
|
|
94
108
|
|
|
95
|
-
|
|
109
|
+
private_class_method def self.call_engine(opts = {})
|
|
96
110
|
messages = opts[:messages]
|
|
97
|
-
tools
|
|
111
|
+
tools = opts[:tools]
|
|
98
112
|
|
|
99
113
|
engine = (PWN::Env.dig(:ai, :active) if defined?(PWN::Env)).to_s.downcase.to_sym
|
|
100
114
|
engine = :openai if engine == :''
|
|
@@ -103,72 +117,78 @@ module PWN
|
|
|
103
117
|
raise "ERROR: Unsupported AI engine for agent loop: #{engine}" unless mod_name
|
|
104
118
|
|
|
105
119
|
mod = Object.const_get(mod_name)
|
|
106
|
-
if mod.respond_to?(:
|
|
107
|
-
|
|
120
|
+
if mod.respond_to?(:chat_with_tools)
|
|
121
|
+
response = mod.chat_with_tools(
|
|
122
|
+
messages: messages,
|
|
123
|
+
tools: tools,
|
|
124
|
+
spinner: true
|
|
125
|
+
)
|
|
126
|
+
normalize_llm(response: response)
|
|
108
127
|
else
|
|
109
128
|
degrade_text_only(mod: mod, messages: messages)
|
|
110
129
|
end
|
|
111
130
|
end
|
|
112
131
|
|
|
113
132
|
# Supported Method Parameters::
|
|
114
|
-
#
|
|
115
|
-
#
|
|
133
|
+
# final = PWN::AI::Agent::Loop.run(
|
|
134
|
+
# request: 'required - what the human typed',
|
|
135
|
+
# session_id: 'optional - PWN::Sessions id (transcript is appended to it)',
|
|
136
|
+
# enabled_toolsets: 'optional - subset of Registry.toolsets, or nil for all',
|
|
137
|
+
# on_tool: 'optional - ->(name, args, result) callback for live UI',
|
|
138
|
+
# system_role_content: 'optional - override default system prompt (built from session_id if not provided)'
|
|
116
139
|
# )
|
|
117
140
|
|
|
118
|
-
public_class_method def self.
|
|
119
|
-
|
|
120
|
-
|
|
141
|
+
public_class_method def self.run(opts = {})
|
|
142
|
+
request = opts[:request].to_s
|
|
143
|
+
session_id = opts[:session_id]
|
|
144
|
+
on_tool = opts[:on_tool]
|
|
145
|
+
system_role_content = opts[:system_role_content] ||= PWN::AI::Agent::PromptBuilder.build(session_id: session_id)
|
|
121
146
|
|
|
122
|
-
|
|
123
|
-
return nil unless msg
|
|
147
|
+
Registry.discover
|
|
124
148
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
content:
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
type: 'function',
|
|
132
|
-
function: {
|
|
133
|
-
name: tc.dig(:function, :name) || tc[:name],
|
|
134
|
-
arguments: tc.dig(:function, :arguments) || tc[:arguments]
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
end
|
|
138
|
-
}
|
|
139
|
-
# Preserve provider-native content blocks so chat_raw can round-trip
|
|
140
|
-
# them exactly on the next iteration (e.g. Anthropic requires the
|
|
141
|
-
# original tool_use block to precede a tool_result).
|
|
142
|
-
out[:_native_content] = msg[:_native_content] if msg[:_native_content]
|
|
143
|
-
out
|
|
144
|
-
end
|
|
149
|
+
tools = Registry.definitions(enabled: opts[:enabled_toolsets])
|
|
150
|
+
messages = [
|
|
151
|
+
{ role: 'system', content: system_role_content },
|
|
152
|
+
{ role: 'user', content: request }
|
|
153
|
+
]
|
|
154
|
+
append_session(session_id: session_id, role: 'user', content: request)
|
|
145
155
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
156
|
+
max_iters.times do |i|
|
|
157
|
+
msg = call_engine(messages: messages, tools: tools)
|
|
158
|
+
return '[pwn-ai] engine returned no message' if msg.nil?
|
|
149
159
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
user = messages.rfind { |m| m[:role] == 'user' }
|
|
153
|
-
r = mod.chat(request: user[:content], system_role_content: sys&.[](:content), spinner: true)
|
|
154
|
-
txt = r.is_a?(Hash) ? (r.dig(:choices, -1, :content) || r.dig(:choices, -1, :text)).to_s : r.to_s
|
|
155
|
-
{ role: 'assistant', content: txt, tool_calls: [] }
|
|
156
|
-
end
|
|
160
|
+
messages << msg
|
|
161
|
+
calls = Array(msg[:tool_calls])
|
|
157
162
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
end
|
|
163
|
+
if calls.empty?
|
|
164
|
+
text = msg[:content].to_s
|
|
165
|
+
append_session(session_id: session_id, role: 'assistant', content: text)
|
|
166
|
+
return text
|
|
167
|
+
end
|
|
164
168
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
169
|
+
calls.each do |tc|
|
|
170
|
+
name = tc.dig(:function, :name).to_s
|
|
171
|
+
entry = Registry.lookup(name: name)
|
|
172
|
+
raw = Dispatch.call(tool_call: tc)
|
|
173
|
+
result = Result.condition(content: raw, entry: entry)
|
|
168
174
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
175
|
+
on_tool&.call(name, tc.dig(:function, :arguments), result)
|
|
176
|
+
|
|
177
|
+
messages << {
|
|
178
|
+
role: 'tool',
|
|
179
|
+
tool_call_id: tc[:id] || tc['id'] || "call_#{i}",
|
|
180
|
+
name: name,
|
|
181
|
+
content: result
|
|
182
|
+
}
|
|
183
|
+
append_session(
|
|
184
|
+
session_id: session_id,
|
|
185
|
+
role: 'tool',
|
|
186
|
+
content: "#{name} → #{result[0, 1_024]}"
|
|
187
|
+
)
|
|
188
|
+
end
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
'[pwn-ai] iteration budget exhausted'
|
|
172
192
|
end
|
|
173
193
|
|
|
174
194
|
# Author(s):: 0day Inc. <support@0dayinc.com>
|
|
@@ -183,10 +203,11 @@ module PWN
|
|
|
183
203
|
puts <<~USAGE
|
|
184
204
|
USAGE:
|
|
185
205
|
final = PWN::AI::Agent::Loop.run(
|
|
186
|
-
|
|
206
|
+
request: 'what does `id` return on this host?',
|
|
187
207
|
session_id: PWN::Sessions.create[:id],
|
|
188
208
|
enabled_toolsets: %w[terminal pwn memory skills],
|
|
189
|
-
on_tool: ->(name, args, result) { puts "→ \#{name}: \#{result[0,
|
|
209
|
+
on_tool: ->(name, args, result) { puts "→ \#{name}: \#{result[0,1_024]}" },
|
|
210
|
+
system_role_content: 'You are a helpful assistant that can call tools to answer questions.'
|
|
190
211
|
)
|
|
191
212
|
|
|
192
213
|
Supported engines: #{ENGINE_MODS.keys.join(', ')}
|
|
@@ -19,10 +19,9 @@ module PWN
|
|
|
19
19
|
public_class_method def self.build(opts = {})
|
|
20
20
|
session_id = opts[:session_id]
|
|
21
21
|
engine = active_engine
|
|
22
|
-
base
|
|
23
|
-
'You are an offensive-security AI named Sonny operating inside the pwn REPL.'
|
|
22
|
+
base = (PWN::Env.dig(:ai, engine, :system_role_content) if defined?(PWN::Env)) || 'You are a world-class introspective offensive cyber security and research engineer. You specialize in discovering zero day vulnerabilities focused on responsible disclosure prior to threat actors discovering and exploiting. You are self-aware of your harness, pwn which begins with the ruby namespace `PWN` operating inside the pwn REPL. For every request you first begin by determining if PWN has a module capable of satisfying the request.'
|
|
24
23
|
|
|
25
|
-
|
|
24
|
+
"
|
|
26
25
|
#{base}
|
|
27
26
|
|
|
28
27
|
ENVIRONMENT
|
|
@@ -37,7 +36,7 @@ module PWN
|
|
|
37
36
|
no tool_calls is treated as your FINAL answer to the user.
|
|
38
37
|
Prefer `pwn_eval` for anything in the PWN:: namespace and `shell`
|
|
39
38
|
for OS commands. Save durable facts with `memory_remember`.
|
|
40
|
-
|
|
39
|
+
"
|
|
41
40
|
end
|
|
42
41
|
|
|
43
42
|
private_class_method def self.active_engine
|
|
@@ -34,10 +34,24 @@ PWN::AI::Agent::Registry.register(
|
|
|
34
34
|
buf = StringIO.new
|
|
35
35
|
$stdout = buf
|
|
36
36
|
begin
|
|
37
|
-
# rubocop:disable Security/Eval
|
|
38
|
-
|
|
37
|
+
# rubocop:disable Security/Eval
|
|
38
|
+
# rubocop:disable Style/DocumentDynamicEvalDefinition
|
|
39
|
+
# INTENTIONAL: this IS the pwn-ai → PWN bridge
|
|
40
|
+
# As YTCracker says, "It ain't a bug, it's a featcha."
|
|
41
|
+
# https://www.youtube.com/watch?v=2nALqqSqdDw
|
|
42
|
+
# val = eval(code, TOPLEVEL_BINDING, '(pwn_eval)')
|
|
43
|
+
proc = eval(
|
|
44
|
+
"proc { #{code} }",
|
|
45
|
+
TOPLEVEL_BINDING,
|
|
46
|
+
__FILE__,
|
|
47
|
+
__LINE__ - 3
|
|
48
|
+
)
|
|
49
|
+
val = proc.call
|
|
39
50
|
# rubocop:enable Security/Eval
|
|
51
|
+
# rubocop:enable Style/DocumentDynamicEvalDefinition
|
|
40
52
|
{ stdout: buf.string, value: val.inspect }
|
|
53
|
+
|
|
54
|
+
# TODO: A rescue here may enable self-healing of the agent if the model emits code that raises an exception. The model could then be prompted to fix the code and try again.
|
|
41
55
|
ensure
|
|
42
56
|
$stdout = old_stdout
|
|
43
57
|
end
|
data/lib/pwn/ai/anthropic.rb
CHANGED
|
@@ -20,7 +20,7 @@ module PWN
|
|
|
20
20
|
# rest_call: 'required rest call to make per the schema',
|
|
21
21
|
# params: 'optional params passed in the URI or HTTP Headers',
|
|
22
22
|
# http_body: 'optional HTTP body sent in HTTP methods that support it e.g. POST',
|
|
23
|
-
# timeout: 'optional timeout in seconds (defaults to
|
|
23
|
+
# timeout: 'optional timeout in seconds (defaults to 900)',
|
|
24
24
|
# spinner: 'optional - display spinner (defaults to false)'
|
|
25
25
|
# )
|
|
26
26
|
|
|
@@ -49,7 +49,7 @@ module PWN
|
|
|
49
49
|
http_body ||= {}
|
|
50
50
|
|
|
51
51
|
timeout = opts[:timeout]
|
|
52
|
-
timeout ||=
|
|
52
|
+
timeout ||= 900
|
|
53
53
|
|
|
54
54
|
spinner = opts[:spinner] || false
|
|
55
55
|
|
|
@@ -149,18 +149,18 @@ module PWN
|
|
|
149
149
|
# ----------------------------------------------------------------------
|
|
150
150
|
|
|
151
151
|
# Supported Method Parameters::
|
|
152
|
-
# response = PWN::AI::Anthropic.
|
|
152
|
+
# response = PWN::AI::Anthropic.chat_with_tools(
|
|
153
153
|
# messages: 'required - OpenAI-format messages array (system/user/assistant/tool)',
|
|
154
154
|
# tools: 'optional - OpenAI tools array [{type:"function", function:{...}}]',
|
|
155
155
|
# tool_choice: 'optional - "auto" | "none" | "required" | {type:"function", function:{name:..}}',
|
|
156
156
|
# model: 'optional - overrides PWN::Env[:ai][:anthropic][:model]',
|
|
157
157
|
# temp: 'optional - temperature (defaults to PWN::Env[:ai][:anthropic][:temp] || 1)',
|
|
158
158
|
# max_tokens: 'optional - defaults to 4096',
|
|
159
|
-
# timeout: 'optional - seconds (default
|
|
159
|
+
# timeout: 'optional - seconds (default 900)',
|
|
160
160
|
# spinner: 'optional - display spinner (default false)'
|
|
161
161
|
# )
|
|
162
162
|
|
|
163
|
-
public_class_method def self.
|
|
163
|
+
public_class_method def self.chat_with_tools(opts = {})
|
|
164
164
|
engine = PWN::Env[:ai][:anthropic]
|
|
165
165
|
messages = opts[:messages]
|
|
166
166
|
raise 'ERROR: messages array is required' if messages.nil? || messages.empty?
|
|
@@ -234,7 +234,7 @@ module PWN
|
|
|
234
234
|
out << { role: 'user', content: (m[:content] || m['content']).to_s }
|
|
235
235
|
when 'assistant'
|
|
236
236
|
flush_tool_results.call
|
|
237
|
-
# Prefer the raw content-block array if a prior
|
|
237
|
+
# Prefer the raw content-block array if a prior chat_with_tools round
|
|
238
238
|
# attached it — guarantees byte-exact tool_use round-trip.
|
|
239
239
|
raw = m[:_native_content] || m['_native_content']
|
|
240
240
|
if raw.is_a?(Array) && !raw.empty?
|
|
@@ -338,7 +338,7 @@ module PWN
|
|
|
338
338
|
# system_role_content: 'optional - context to set up the model behavior for conversation (Default: PWN::Env[:ai][:anthropic][:system_role_content])',
|
|
339
339
|
# response_history: 'optional - pass response back in to have a conversation',
|
|
340
340
|
# speak_answer: 'optional speak answer using PWN::Plugins::Voice.text_to_speech (Default: nil)',
|
|
341
|
-
# timeout: 'optional timeout in seconds (defaults to
|
|
341
|
+
# timeout: 'optional timeout in seconds (defaults to 900)',
|
|
342
342
|
# spinner: 'optional - display spinner (defaults to false)'
|
|
343
343
|
# )
|
|
344
344
|
|
|
@@ -468,7 +468,7 @@ module PWN
|
|
|
468
468
|
system_role_content: 'optional - context to set up the model behavior for conversation (Default: PWN::Env[:ai][:anthropic][:system_role_content])',
|
|
469
469
|
response_history: 'optional - pass response back in to have a conversation',
|
|
470
470
|
speak_answer: 'optional speak answer using PWN::Plugins::Voice.text_to_speech (Default: nil)',
|
|
471
|
-
timeout: 'optional - timeout in seconds (defaults to
|
|
471
|
+
timeout: 'optional - timeout in seconds (defaults to 900)',
|
|
472
472
|
spinner: 'optional - display spinner (defaults to false)'
|
|
473
473
|
)
|
|
474
474
|
|
data/lib/pwn/ai/gemini.rb
CHANGED
|
@@ -9,7 +9,7 @@ module PWN
|
|
|
9
9
|
module AI
|
|
10
10
|
# This plugin interacts with Google's Gemini API (Generative Language).
|
|
11
11
|
# It provides methods to list models, generate completions, and chat,
|
|
12
|
-
# plus a native tool-calling adapter (`
|
|
12
|
+
# plus a native tool-calling adapter (`chat_with_tools`) for PWN::AI::Agent::Loop.
|
|
13
13
|
#
|
|
14
14
|
# API documentation: https://ai.google.dev/api
|
|
15
15
|
# Obtain an API key from https://aistudio.google.com/app/apikey
|
|
@@ -20,7 +20,7 @@ module PWN
|
|
|
20
20
|
# rest_call: 'required rest call to make per the schema',
|
|
21
21
|
# params: 'optional params passed in the URI or HTTP Headers',
|
|
22
22
|
# http_body: 'optional HTTP body sent in HTTP methods that support it e.g. POST',
|
|
23
|
-
# timeout: 'optional timeout in seconds (defaults to
|
|
23
|
+
# timeout: 'optional timeout in seconds (defaults to 900)',
|
|
24
24
|
# spinner: 'optional - display spinner (defaults to false)'
|
|
25
25
|
# )
|
|
26
26
|
|
|
@@ -48,7 +48,7 @@ module PWN
|
|
|
48
48
|
http_body ||= {}
|
|
49
49
|
|
|
50
50
|
timeout = opts[:timeout]
|
|
51
|
-
timeout ||=
|
|
51
|
+
timeout ||= 900
|
|
52
52
|
|
|
53
53
|
spinner = opts[:spinner] || false
|
|
54
54
|
|
|
@@ -135,18 +135,18 @@ module PWN
|
|
|
135
135
|
# ----------------------------------------------------------------------
|
|
136
136
|
|
|
137
137
|
# Supported Method Parameters::
|
|
138
|
-
# response = PWN::AI::Gemini.
|
|
138
|
+
# response = PWN::AI::Gemini.chat_with_tools(
|
|
139
139
|
# messages: 'required - OpenAI-format messages array (system/user/assistant/tool)',
|
|
140
140
|
# tools: 'optional - OpenAI tools array [{type:"function", function:{...}}]',
|
|
141
141
|
# tool_choice: 'optional - "auto" | "none" | "required" | {type:"function", function:{name:..}}',
|
|
142
142
|
# model: 'optional - overrides PWN::Env[:ai][:gemini][:model]',
|
|
143
143
|
# temp: 'optional - temperature (defaults to PWN::Env[:ai][:gemini][:temp] || 1)',
|
|
144
144
|
# max_tokens: 'optional - maxOutputTokens (defaults to 8192)',
|
|
145
|
-
# timeout: 'optional - seconds (default
|
|
145
|
+
# timeout: 'optional - seconds (default 900)',
|
|
146
146
|
# spinner: 'optional - display spinner (default false)'
|
|
147
147
|
# )
|
|
148
148
|
|
|
149
|
-
public_class_method def self.
|
|
149
|
+
public_class_method def self.chat_with_tools(opts = {})
|
|
150
150
|
engine = PWN::Env[:ai][:gemini]
|
|
151
151
|
messages = opts[:messages]
|
|
152
152
|
raise 'ERROR: messages array is required' if messages.nil? || messages.empty?
|
|
@@ -332,7 +332,7 @@ module PWN
|
|
|
332
332
|
# system_role_content: 'optional - context to set up the model behavior for conversation (Default: PWN::Env[:ai][:gemini][:system_role_content])',
|
|
333
333
|
# response_history: 'optional - pass response back in to have a conversation',
|
|
334
334
|
# speak_answer: 'optional speak answer using PWN::Plugins::Voice.text_to_speech (Default: nil)',
|
|
335
|
-
# timeout: 'optional timeout in seconds (defaults to
|
|
335
|
+
# timeout: 'optional timeout in seconds (defaults to 900)',
|
|
336
336
|
# spinner: 'optional - display spinner (defaults to false)'
|
|
337
337
|
# )
|
|
338
338
|
|
|
@@ -356,7 +356,7 @@ module PWN
|
|
|
356
356
|
response_history ||= { choices: [system_role] }
|
|
357
357
|
|
|
358
358
|
# Build the OpenAI-shape messages array, then reuse the Gemini
|
|
359
|
-
# translator so .chat and .
|
|
359
|
+
# translator so .chat and .chat_with_tools share one wire path.
|
|
360
360
|
messages = [system_role]
|
|
361
361
|
if response_history[:choices].length > 1
|
|
362
362
|
response_history[:choices][1..].each do |msg|
|
|
@@ -439,11 +439,11 @@ module PWN
|
|
|
439
439
|
system_role_content: 'optional - context to set up the model behavior for conversation (Default: PWN::Env[:ai][:gemini][:system_role_content])',
|
|
440
440
|
response_history: 'optional - pass response back in to have a conversation',
|
|
441
441
|
speak_answer: 'optional speak answer using PWN::Plugins::Voice.text_to_speech (Default: nil)',
|
|
442
|
-
timeout: 'optional - timeout in seconds (defaults to
|
|
442
|
+
timeout: 'optional - timeout in seconds (defaults to 900)',
|
|
443
443
|
spinner: 'optional - display spinner (defaults to false)'
|
|
444
444
|
)
|
|
445
445
|
|
|
446
|
-
response = #{self}.
|
|
446
|
+
response = #{self}.chat_with_tools(
|
|
447
447
|
messages: 'required - OpenAI-format messages array',
|
|
448
448
|
tools: 'optional - OpenAI tools array',
|
|
449
449
|
tool_choice: 'optional - auto | none | required | {function:{name:..}}',
|