n2b 0.3.1 → 0.5.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/README.md +349 -31
- data/bin/n2b-test-jira +273 -0
- data/lib/n2b/base.rb +205 -42
- data/lib/n2b/cli.rb +251 -34
- data/lib/n2b/config/models.yml +47 -0
- data/lib/n2b/irb.rb +1 -1
- data/lib/n2b/jira_client.rb +753 -0
- data/lib/n2b/llm/claude.rb +14 -3
- data/lib/n2b/llm/gemini.rb +13 -5
- data/lib/n2b/llm/ollama.rb +129 -0
- data/lib/n2b/llm/open_ai.rb +13 -3
- data/lib/n2b/llm/open_router.rb +116 -0
- data/lib/n2b/model_config.rb +139 -0
- data/lib/n2b/version.rb +1 -1
- metadata +10 -4
data/lib/n2b/cli.rb
CHANGED
@@ -1,4 +1,6 @@
|
|
1
|
-
|
1
|
+
require_relative 'jira_client' # For N2B::JiraClient
|
2
|
+
|
3
|
+
module N2B
|
2
4
|
class CLI < Base
|
3
5
|
def self.run(args)
|
4
6
|
new(args).execute
|
@@ -10,7 +12,8 @@ module N2B
|
|
10
12
|
end
|
11
13
|
|
12
14
|
def execute
|
13
|
-
|
15
|
+
# Pass advanced_config flag to get_config
|
16
|
+
config = get_config(reconfigure: @options[:config], advanced_flow: @options[:advanced_config])
|
14
17
|
user_input = @args.join(' ') # All remaining args form user input/prompt addition
|
15
18
|
|
16
19
|
if @options[:diff]
|
@@ -37,17 +40,113 @@ module N2B
|
|
37
40
|
requirements_filepath = @options[:requirements]
|
38
41
|
user_prompt_addition = @args.join(' ') # All remaining args are user prompt addition
|
39
42
|
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
+
# Jira Ticket Information
|
44
|
+
jira_ticket = @options[:jira_ticket]
|
45
|
+
jira_update_flag = @options[:jira_update] # true, false, or nil
|
46
|
+
|
47
|
+
requirements_content = nil # Initialize requirements_content
|
48
|
+
|
49
|
+
if jira_ticket
|
50
|
+
puts "Jira ticket specified: #{jira_ticket}"
|
51
|
+
if config['jira'] && config['jira']['domain'] && config['jira']['email'] && config['jira']['api_key']
|
52
|
+
begin
|
53
|
+
jira_client = N2B::JiraClient.new(config) # Pass the whole config
|
54
|
+
puts "Fetching Jira ticket details..."
|
55
|
+
# If a requirements file is also provided, the Jira ticket will take precedence.
|
56
|
+
# Or, we could append/prepend. For now, Jira overwrites.
|
57
|
+
requirements_content = jira_client.fetch_ticket(jira_ticket)
|
58
|
+
puts "Successfully fetched Jira ticket details."
|
59
|
+
# The fetched content is now in requirements_content and will be passed to analyze_diff
|
60
|
+
rescue N2B::JiraClient::JiraApiError => e
|
61
|
+
puts "Error fetching Jira ticket: #{e.message}"
|
62
|
+
puts "Proceeding with diff analysis without Jira ticket details."
|
63
|
+
rescue ArgumentError => e # Catches missing Jira config in JiraClient.new
|
64
|
+
puts "Jira configuration error: #{e.message}"
|
65
|
+
puts "Please ensure Jira is configured correctly using 'n2b -c'."
|
66
|
+
puts "Proceeding with diff analysis without Jira ticket details."
|
67
|
+
rescue StandardError => e
|
68
|
+
puts "An unexpected error occurred while fetching Jira ticket: #{e.message}"
|
69
|
+
puts "Proceeding with diff analysis without Jira ticket details."
|
70
|
+
end
|
71
|
+
else
|
72
|
+
puts "Jira configuration is missing or incomplete in N2B settings."
|
73
|
+
puts "Please configure Jira using 'n2b -c' to fetch ticket details."
|
74
|
+
puts "Proceeding with diff analysis without Jira ticket details."
|
75
|
+
end
|
76
|
+
# Handling of jira_update_flag can be done elsewhere, e.g., after analysis
|
77
|
+
if jira_update_flag == true
|
78
|
+
puts "Note: Jira ticket update (--jira-update) is flagged."
|
79
|
+
# Actual update logic will be separate
|
80
|
+
elsif jira_update_flag == false
|
81
|
+
puts "Note: Jira ticket will not be updated (--jira-no-update)."
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
# Load requirements from file if no Jira ticket was fetched or if specifically desired even with Jira.
|
86
|
+
# Current logic: Jira fetch, if successful, populates requirements_content.
|
87
|
+
# If Jira not specified, or fetch failed, try to load from file.
|
88
|
+
if requirements_content.nil? && requirements_filepath
|
89
|
+
if File.exist?(requirements_filepath)
|
90
|
+
puts "Loading requirements from file: #{requirements_filepath}"
|
91
|
+
requirements_content = File.read(requirements_filepath)
|
92
|
+
else
|
43
93
|
puts "Error: Requirements file not found: #{requirements_filepath}"
|
44
|
-
exit
|
94
|
+
# Decide if to exit or proceed. For now, proceed.
|
95
|
+
puts "Proceeding with diff analysis without file-based requirements."
|
45
96
|
end
|
46
|
-
|
97
|
+
elsif requirements_content && requirements_filepath
|
98
|
+
puts "Note: Both Jira ticket and requirements file were provided. Using Jira ticket content for analysis."
|
47
99
|
end
|
48
100
|
|
49
101
|
diff_output = execute_vcs_diff(vcs_type, @options[:branch])
|
50
|
-
analyze_diff(diff_output, config, user_prompt_addition, requirements_content)
|
102
|
+
analysis_result = analyze_diff(diff_output, config, user_prompt_addition, requirements_content) # Store the result
|
103
|
+
|
104
|
+
# --- Jira Update Logic ---
|
105
|
+
if jira_ticket && analysis_result && !analysis_result.empty?
|
106
|
+
# Check if Jira config is valid for updating
|
107
|
+
if config['jira'] && config['jira']['domain'] && config['jira']['email'] && config['jira']['api_key']
|
108
|
+
jira_comment_data = format_analysis_for_jira(analysis_result)
|
109
|
+
proceed_with_update = false
|
110
|
+
|
111
|
+
if jira_update_flag == true # --jira-update used
|
112
|
+
proceed_with_update = true
|
113
|
+
elsif jira_update_flag.nil? # Neither --jira-update nor --jira-no-update used
|
114
|
+
puts "\nWould you like to update Jira ticket #{jira_ticket} with this analysis? (y/n)"
|
115
|
+
user_choice = $stdin.gets.chomp.downcase
|
116
|
+
proceed_with_update = user_choice == 'y'
|
117
|
+
end # If jira_update_flag is false, proceed_with_update remains false
|
118
|
+
|
119
|
+
if proceed_with_update
|
120
|
+
begin
|
121
|
+
# Re-instantiate JiraClient or use an existing one if available and in scope
|
122
|
+
# For safety and simplicity here, re-instantiate with current config.
|
123
|
+
update_jira_client = N2B::JiraClient.new(config)
|
124
|
+
puts "Updating Jira ticket #{jira_ticket}..."
|
125
|
+
if update_jira_client.update_ticket(jira_ticket, jira_comment_data)
|
126
|
+
puts "Jira ticket #{jira_ticket} updated successfully."
|
127
|
+
else
|
128
|
+
# update_ticket currently returns true/false, but might raise error for http issues
|
129
|
+
puts "Failed to update Jira ticket #{jira_ticket}. The client did not report an error, but the update may not have completed."
|
130
|
+
end
|
131
|
+
rescue N2B::JiraClient::JiraApiError => e
|
132
|
+
puts "Error updating Jira ticket: #{e.message}"
|
133
|
+
rescue ArgumentError => e # From JiraClient.new if config is suddenly invalid
|
134
|
+
puts "Jira configuration error before update: #{e.message}"
|
135
|
+
rescue StandardError => e
|
136
|
+
puts "An unexpected error occurred while updating Jira ticket: #{e.message}"
|
137
|
+
end
|
138
|
+
else
|
139
|
+
puts "Jira ticket update skipped."
|
140
|
+
end
|
141
|
+
else
|
142
|
+
puts "Jira configuration is missing or incomplete. Cannot proceed with Jira update."
|
143
|
+
end
|
144
|
+
elsif jira_ticket && (analysis_result.nil? || analysis_result.empty?)
|
145
|
+
puts "Skipping Jira update as analysis result was empty or not generated."
|
146
|
+
end
|
147
|
+
# --- End of Jira Update Logic ---
|
148
|
+
|
149
|
+
analysis_result # Return analysis_result from handle_diff_analysis
|
51
150
|
end
|
52
151
|
|
53
152
|
def get_vcs_type
|
@@ -172,11 +271,11 @@ module N2B
|
|
172
271
|
|
173
272
|
def validate_git_branch_exists(branch)
|
174
273
|
# Check if branch exists locally
|
175
|
-
|
274
|
+
_result = `git rev-parse --verify #{branch} 2>/dev/null`
|
176
275
|
return true if $?.success?
|
177
276
|
|
178
277
|
# Check if branch exists on remote
|
179
|
-
|
278
|
+
_result = `git rev-parse --verify origin/#{branch} 2>/dev/null`
|
180
279
|
return true if $?.success?
|
181
280
|
|
182
281
|
false
|
@@ -284,20 +383,29 @@ REQUIREMENTS_BLOCK
|
|
284
383
|
end
|
285
384
|
|
286
385
|
json_instruction = <<-JSON_INSTRUCTION.strip
|
287
|
-
CRITICAL: Return ONLY a valid JSON object
|
386
|
+
CRITICAL: Return ONLY a valid JSON object.
|
288
387
|
Do not include any explanatory text before or after the JSON.
|
289
388
|
Each error and improvement should include specific file paths and line numbers.
|
290
389
|
|
390
|
+
The JSON object must contain the following keys:
|
391
|
+
- "summary": (string) Brief overall description of the changes.
|
392
|
+
- "ticket_implementation_summary": (string) A concise summary of what was implemented or achieved in relation to the ticket's goals, based *only* on the provided diff. This is for developer status updates and Jira comments.
|
393
|
+
- "errors": (list of strings) Potential bugs or issues found.
|
394
|
+
- "improvements": (list of strings) Suggestions for code quality, style, performance, or security.
|
395
|
+
- "test_coverage": (string) Assessment of test coverage for the changes.
|
396
|
+
- "requirements_evaluation": (string, include only if requirements were provided in the prompt) Evaluation of how the changes meet the provided requirements.
|
397
|
+
|
291
398
|
Example format:
|
292
399
|
{
|
293
|
-
"summary": "
|
400
|
+
"summary": "Refactored the user authentication module and added password complexity checks.",
|
401
|
+
"ticket_implementation_summary": "Implemented the core logic for user password updates and strengthened security by adding complexity validation as per the ticket's primary goal. Some UI elements are pending.",
|
294
402
|
"errors": [
|
295
|
-
"lib/example.rb line 42: Potential null pointer exception when accessing user.name without checking if user is nil",
|
296
|
-
"src/main.js lines 15-20: Missing error handling for async operation"
|
403
|
+
"lib/example.rb line 42: Potential null pointer exception when accessing user.name without checking if user is nil.",
|
404
|
+
"src/main.js lines 15-20: Missing error handling for async operation."
|
297
405
|
],
|
298
406
|
"improvements": [
|
299
|
-
"lib/example.rb line 30: Consider using a constant for the magic number 42",
|
300
|
-
"src/utils.py lines 5-10: This method could be simplified using list comprehension"
|
407
|
+
"lib/example.rb line 30: Consider using a constant for the magic number 42.",
|
408
|
+
"src/utils.py lines 5-10: This method could be simplified using list comprehension."
|
301
409
|
],
|
302
410
|
"test_coverage": "Good: New functionality in lib/example.rb has corresponding tests in test/example_test.rb. Missing: No tests for error handling edge cases in the new validation method.",
|
303
411
|
"requirements_evaluation": "✅ IMPLEMENTED: User authentication feature is fully implemented in auth.rb. ⚠️ PARTIALLY IMPLEMENTED: Error handling is present but lacks specific error codes. ❌ NOT IMPLEMENTED: Email notifications are not addressed in this diff."
|
@@ -352,13 +460,46 @@ JSON_INSTRUCTION
|
|
352
460
|
puts "\nRequirements Evaluation:"
|
353
461
|
puts requirements_eval
|
354
462
|
end
|
463
|
+
|
464
|
+
puts "\nTicket Implementation Summary:"
|
465
|
+
impl_summary = analysis_result['ticket_implementation_summary']
|
466
|
+
puts impl_summary && !impl_summary.to_s.strip.empty? ? impl_summary : "No implementation summary provided."
|
467
|
+
|
355
468
|
puts "-------------------"
|
469
|
+
return analysis_result # Return the parsed hash
|
356
470
|
rescue JSON::ParserError => e # Handles cases where the JSON string (even fallback) is malformed
|
357
471
|
puts "Critical Error: Failed to parse JSON response for diff analysis: #{e.message}"
|
358
472
|
puts "Raw response was: #{analysis_json_str}"
|
473
|
+
return {} # Return empty hash on parsing error
|
359
474
|
end
|
360
475
|
end
|
361
476
|
|
477
|
+
private # Make sure new helper is private
|
478
|
+
|
479
|
+
def format_analysis_for_jira(analysis_result)
|
480
|
+
return "No analysis result available." if analysis_result.nil? || analysis_result.empty?
|
481
|
+
|
482
|
+
# Return structured data for ADF formatting
|
483
|
+
{
|
484
|
+
implementation_summary: analysis_result['ticket_implementation_summary']&.strip,
|
485
|
+
technical_summary: analysis_result['summary']&.strip,
|
486
|
+
issues: format_issues_for_adf(analysis_result['errors']),
|
487
|
+
improvements: format_improvements_for_adf(analysis_result['improvements']),
|
488
|
+
test_coverage: analysis_result['test_coverage']&.strip,
|
489
|
+
requirements_evaluation: analysis_result['requirements_evaluation']&.strip
|
490
|
+
}
|
491
|
+
end
|
492
|
+
|
493
|
+
def format_issues_for_adf(errors)
|
494
|
+
return [] unless errors.is_a?(Array) && errors.any?
|
495
|
+
errors.map(&:strip).reject(&:empty?)
|
496
|
+
end
|
497
|
+
|
498
|
+
def format_improvements_for_adf(improvements)
|
499
|
+
return [] unless improvements.is_a?(Array) && improvements.any?
|
500
|
+
improvements.map(&:strip).reject(&:empty?)
|
501
|
+
end
|
502
|
+
|
362
503
|
def extract_json_from_response(response)
|
363
504
|
# First try to parse the response as-is
|
364
505
|
begin
|
@@ -413,7 +554,7 @@ JSON_INSTRUCTION
|
|
413
554
|
# Parse hunk header (e.g., "@@ -10,7 +10,8 @@")
|
414
555
|
match = line.match(/@@ -(\d+),?\d* \+(\d+),?\d* @@/)
|
415
556
|
if match
|
416
|
-
|
557
|
+
_old_start = match[1].to_i
|
417
558
|
new_start = match[2].to_i
|
418
559
|
|
419
560
|
# Use the new file line numbers for context
|
@@ -457,6 +598,10 @@ JSON_INSTRUCTION
|
|
457
598
|
N2M::Llm::Claude.new(config)
|
458
599
|
when 'gemini'
|
459
600
|
N2M::Llm::Gemini.new(config)
|
601
|
+
when 'openrouter'
|
602
|
+
N2M::Llm::OpenRouter.new(config)
|
603
|
+
when 'ollama'
|
604
|
+
N2M::Llm::Ollama.new(config)
|
460
605
|
else
|
461
606
|
# Should not happen if config is validated, but as a safeguard:
|
462
607
|
raise N2B::Error, "Unsupported LLM service: #{llm_service_name}"
|
@@ -466,6 +611,13 @@ JSON_INSTRUCTION
|
|
466
611
|
response_json_str
|
467
612
|
rescue N2B::LlmApiError => e # This catches errors from analyze_code_diff
|
468
613
|
puts "Error communicating with the LLM: #{e.message}"
|
614
|
+
|
615
|
+
# Check if it might be a model-related error
|
616
|
+
if e.message.include?('model') || e.message.include?('Model') || e.message.include?('invalid') || e.message.include?('not found')
|
617
|
+
puts "\nThis might be due to an invalid or unsupported model configuration."
|
618
|
+
puts "Run 'n2b -c' to reconfigure your model settings."
|
619
|
+
end
|
620
|
+
|
469
621
|
return '{"summary": "Error: Could not analyze diff due to LLM API error.", "errors": [], "improvements": []}'
|
470
622
|
end
|
471
623
|
end
|
@@ -485,7 +637,23 @@ JSON_INSTRUCTION
|
|
485
637
|
|
486
638
|
def call_llm(prompt, config)
|
487
639
|
begin # Added begin for LlmApiError rescue
|
488
|
-
|
640
|
+
llm_service_name = config['llm']
|
641
|
+
llm = case llm_service_name
|
642
|
+
when 'openai'
|
643
|
+
N2M::Llm::OpenAi.new(config)
|
644
|
+
when 'claude'
|
645
|
+
N2M::Llm::Claude.new(config)
|
646
|
+
when 'gemini'
|
647
|
+
N2M::Llm::Gemini.new(config)
|
648
|
+
when 'openrouter'
|
649
|
+
N2M::Llm::OpenRouter.new(config)
|
650
|
+
when 'ollama'
|
651
|
+
N2M::Llm::Ollama.new(config)
|
652
|
+
else
|
653
|
+
# Fallback or error, though config validation should prevent this
|
654
|
+
puts "Warning: Unsupported LLM service '#{llm_service_name}' configured. Falling back to Claude."
|
655
|
+
N2M::Llm::Claude.new(config)
|
656
|
+
end
|
489
657
|
|
490
658
|
# This content is specific to bash command generation
|
491
659
|
content = <<-EOF
|
@@ -508,24 +676,26 @@ JSON_INSTRUCTION
|
|
508
676
|
# which implies it expects a Hash. Let's ensure call_llm returns a Hash.
|
509
677
|
# This internal JSON parsing is for the *content* of a successful LLM response.
|
510
678
|
# The LlmApiError for network/auth issues should be caught before this.
|
511
|
-
|
512
|
-
# Check if response_json_str is already a Hash (parsed JSON)
|
513
|
-
if response_json_str.is_a?(Hash)
|
514
|
-
response_json_str
|
515
|
-
else
|
679
|
+
begin
|
516
680
|
parsed_response = JSON.parse(response_json_str)
|
517
681
|
parsed_response
|
682
|
+
rescue JSON::ParserError => e
|
683
|
+
puts "Error parsing LLM response JSON for command generation: #{e.message}"
|
684
|
+
# This is a fallback for when the LLM response *content* is not valid JSON.
|
685
|
+
{ "commands" => ["echo 'Error: LLM returned invalid JSON content.'"], "explanation" => "The response from the language model was not valid JSON." }
|
518
686
|
end
|
519
|
-
rescue
|
520
|
-
puts "Error
|
521
|
-
|
522
|
-
|
523
|
-
|
524
|
-
|
525
|
-
|
526
|
-
|
527
|
-
|
528
|
-
|
687
|
+
rescue N2B::LlmApiError => e
|
688
|
+
puts "Error communicating with the LLM: #{e.message}"
|
689
|
+
|
690
|
+
# Check if it might be a model-related error
|
691
|
+
if e.message.include?('model') || e.message.include?('Model') || e.message.include?('invalid') || e.message.include?('not found')
|
692
|
+
puts "\nThis might be due to an invalid or unsupported model configuration."
|
693
|
+
puts "Run 'n2b -c' to reconfigure your model settings."
|
694
|
+
end
|
695
|
+
|
696
|
+
# This is the fallback for LlmApiError (network, auth, etc.)
|
697
|
+
{ "commands" => ["echo 'LLM API error occurred. Please check your configuration and network.'"], "explanation" => "Failed to connect to the LLM." }
|
698
|
+
end
|
529
699
|
end
|
530
700
|
|
531
701
|
def get_user_shell
|
@@ -617,7 +787,16 @@ JSON_INSTRUCTION
|
|
617
787
|
|
618
788
|
|
619
789
|
def parse_options
|
620
|
-
options = {
|
790
|
+
options = {
|
791
|
+
execute: false,
|
792
|
+
config: nil,
|
793
|
+
diff: false,
|
794
|
+
requirements: nil,
|
795
|
+
branch: nil,
|
796
|
+
jira_ticket: nil,
|
797
|
+
jira_update: nil, # Using nil as default, true for --jira-update, false for --jira-no-update
|
798
|
+
advanced_config: false # New option for advanced configuration flow
|
799
|
+
}
|
621
800
|
|
622
801
|
parser = OptionParser.new do |opts|
|
623
802
|
opts.banner = "Usage: n2b [options] [natural language command]"
|
@@ -638,6 +817,18 @@ JSON_INSTRUCTION
|
|
638
817
|
options[:requirements] = file
|
639
818
|
end
|
640
819
|
|
820
|
+
opts.on('-j', '--jira JIRA_ID_OR_URL', 'Jira ticket ID or URL for context or update') do |jira|
|
821
|
+
options[:jira_ticket] = jira
|
822
|
+
end
|
823
|
+
|
824
|
+
opts.on('--jira-update', 'Update the linked Jira ticket (requires -j)') do
|
825
|
+
options[:jira_update] = true
|
826
|
+
end
|
827
|
+
|
828
|
+
opts.on('--jira-no-update', 'Do not update the linked Jira ticket (requires -j)') do
|
829
|
+
options[:jira_update] = false
|
830
|
+
end
|
831
|
+
|
641
832
|
opts.on('-h', '--help', 'Print this help') do
|
642
833
|
puts opts
|
643
834
|
exit
|
@@ -646,6 +837,11 @@ JSON_INSTRUCTION
|
|
646
837
|
opts.on('-c', '--config', 'Configure the API key and model') do
|
647
838
|
options[:config] = true
|
648
839
|
end
|
840
|
+
|
841
|
+
opts.on('--advanced-config', 'Access advanced configuration options including Jira and privacy settings') do
|
842
|
+
options[:advanced_config] = true
|
843
|
+
options[:config] = true # Forcing config mode if advanced is chosen
|
844
|
+
end
|
649
845
|
end
|
650
846
|
|
651
847
|
begin
|
@@ -665,6 +861,27 @@ JSON_INSTRUCTION
|
|
665
861
|
exit 1
|
666
862
|
end
|
667
863
|
|
864
|
+
if options[:jira_update] == true && options[:jira_ticket].nil?
|
865
|
+
puts "Error: --jira-update option requires a Jira ticket to be specified with -j or --jira."
|
866
|
+
puts ""
|
867
|
+
puts parser.help
|
868
|
+
exit 1
|
869
|
+
end
|
870
|
+
|
871
|
+
if options[:jira_update] == false && options[:jira_ticket].nil?
|
872
|
+
puts "Error: --jira-no-update option requires a Jira ticket to be specified with -j or --jira."
|
873
|
+
puts ""
|
874
|
+
puts parser.help
|
875
|
+
exit 1
|
876
|
+
end
|
877
|
+
|
878
|
+
if options[:jira_update] == true && options[:jira_update] == false
|
879
|
+
puts "Error: --jira-update and --jira-no-update are mutually exclusive."
|
880
|
+
puts ""
|
881
|
+
puts parser.help
|
882
|
+
exit 1
|
883
|
+
end
|
884
|
+
|
668
885
|
options
|
669
886
|
end
|
670
887
|
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# N2B Model Configuration
|
2
|
+
# Format: display_name -> api_model_name
|
3
|
+
# Users can select from suggested models or enter custom model names
|
4
|
+
|
5
|
+
claude:
|
6
|
+
suggested:
|
7
|
+
haiku: "claude-3-haiku-20240307"
|
8
|
+
sonnet: "claude-3-sonnet-20240229"
|
9
|
+
sonnet35: "claude-3-5-sonnet-20240620"
|
10
|
+
sonnet37: "claude-3-7-sonnet-20250219"
|
11
|
+
sonnet40: "claude-sonnet-4-20250514"
|
12
|
+
default: "sonnet"
|
13
|
+
|
14
|
+
openai:
|
15
|
+
suggested:
|
16
|
+
gpt-4o: "gpt-4o"
|
17
|
+
gpt-4o-mini: "gpt-4o-mini"
|
18
|
+
o3: "o3"
|
19
|
+
o3-mini: "o3-mini"
|
20
|
+
o3-mini-high: "o3-mini-high"
|
21
|
+
o4: "o4"
|
22
|
+
o4-mini: "o4-mini"
|
23
|
+
o4-mini-high: "o4-mini-high"
|
24
|
+
default: "gpt-4o-mini"
|
25
|
+
|
26
|
+
gemini:
|
27
|
+
suggested:
|
28
|
+
gemini-2.5-flash: "gemini-2.5-flash-preview-05-20"
|
29
|
+
gemini-2.5-pro: "gemini-2.5-pro-preview-05-06"
|
30
|
+
default: "gemini-2.5-flash"
|
31
|
+
|
32
|
+
openrouter:
|
33
|
+
suggested:
|
34
|
+
deepseek-v3: "deepseek-v3-0324"
|
35
|
+
deepseek-r1-llama-8b: "deepseek-r1-distill-llama-8b"
|
36
|
+
llama-3.3-70b: "llama-3.3-70b-instruct"
|
37
|
+
llama-3.3-8b: "llama-3.3-8b-instruct"
|
38
|
+
wayfinder-large: "wayfinder-large-70b-llama-3.3"
|
39
|
+
default: "deepseek-v3"
|
40
|
+
|
41
|
+
ollama:
|
42
|
+
suggested:
|
43
|
+
llama3: "llama3"
|
44
|
+
mistral: "mistral"
|
45
|
+
codellama: "codellama"
|
46
|
+
qwen: "qwen2.5"
|
47
|
+
default: "llama3"
|
data/lib/n2b/irb.rb
CHANGED
@@ -991,7 +991,7 @@ module N2B
|
|
991
991
|
|
992
992
|
# If we still can't fix it, return a formatted version of the original
|
993
993
|
"# Auto-formatted Ticket\n\n```\n#{original_response.inspect}\n```"
|
994
|
-
rescue =>
|
994
|
+
rescue => _e
|
995
995
|
# If anything goes wrong in the fixing process, return a readable version of the original
|
996
996
|
if original_response.is_a?(Hash)
|
997
997
|
# Try to create a readable markdown from the hash
|