kward 0.67.0 → 0.68.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 +26 -0
- data/Gemfile.lock +2 -2
- data/README.md +5 -5
- data/doc/authentication.md +24 -1
- data/doc/configuration.md +9 -2
- data/doc/extensibility.md +1 -1
- data/doc/getting-started.md +4 -6
- data/doc/plugins.md +0 -2
- data/doc/releasing.md +7 -8
- data/doc/rpc.md +6 -6
- data/doc/usage.md +5 -2
- data/doc/web-search.md +2 -2
- data/kward.gemspec +4 -0
- data/lib/kward/agent.rb +29 -2
- data/lib/kward/ansi.rb +3 -0
- data/lib/kward/auth/anthropic_oauth.rb +291 -0
- data/lib/kward/auth/file.rb +2 -0
- data/lib/kward/auth/github_oauth.rb +3 -0
- data/lib/kward/auth/openai_oauth.rb +4 -0
- data/lib/kward/auth/openrouter_api_key.rb +2 -0
- data/lib/kward/cancellation.rb +3 -0
- data/lib/kward/cli/auth_commands.rb +82 -0
- data/lib/kward/cli/commands.rb +222 -0
- data/lib/kward/cli/compaction.rb +25 -0
- data/lib/kward/cli/doctor.rb +121 -0
- data/lib/kward/cli/interactive_turn.rb +225 -0
- data/lib/kward/cli/memory_commands.rb +133 -0
- data/lib/kward/cli/plugins.rb +112 -0
- data/lib/kward/cli/prompt_interface.rb +132 -0
- data/lib/kward/cli/rendering.rb +389 -0
- data/lib/kward/cli/runtime_helpers.rb +159 -0
- data/lib/kward/cli/sessions.rb +376 -0
- data/lib/kward/cli/settings.rb +663 -0
- data/lib/kward/cli/slash_commands.rb +112 -0
- data/lib/kward/cli/stats.rb +64 -0
- data/lib/kward/cli/tool_summaries.rb +153 -0
- data/lib/kward/cli.rb +38 -2790
- data/lib/kward/cli_transcript_formatter.rb +4 -7
- data/lib/kward/clipboard.rb +1 -0
- data/lib/kward/compaction/file_operation_tracker.rb +3 -0
- data/lib/kward/compactor.rb +29 -7
- data/lib/kward/config_files.rb +33 -24
- data/lib/kward/conversation.rb +70 -5
- data/lib/kward/events.rb +2 -0
- data/lib/kward/export_path.rb +2 -0
- data/lib/kward/image_attachments.rb +2 -0
- data/lib/kward/markdown_transcript.rb +2 -0
- data/lib/kward/memory/manager.rb +13 -0
- data/lib/kward/message_access.rb +23 -2
- data/lib/kward/message_text.rb +45 -0
- data/lib/kward/model/chat_invocation.rb +2 -0
- data/lib/kward/model/client.rb +295 -77
- data/lib/kward/model/context_overflow.rb +2 -0
- data/lib/kward/model/context_usage.rb +3 -0
- data/lib/kward/model/model_info.rb +143 -4
- data/lib/kward/model/payloads.rb +166 -13
- data/lib/kward/model/retry_message.rb +2 -0
- data/lib/kward/model/stream_parser.rb +129 -0
- data/lib/kward/pan/server.rb +3 -1
- data/lib/kward/plugin_registry.rb +12 -0
- data/lib/kward/private_file.rb +2 -0
- data/lib/kward/prompt_interface/banner.rb +3 -0
- data/lib/kward/prompt_interface/composer_controller.rb +262 -0
- data/lib/kward/prompt_interface/composer_renderer.rb +172 -0
- data/lib/kward/prompt_interface/composer_state.rb +221 -0
- data/lib/kward/prompt_interface/key_handler.rb +365 -0
- data/lib/kward/prompt_interface/layout.rb +31 -0
- data/lib/kward/prompt_interface/overlay_renderer.rb +111 -0
- data/lib/kward/prompt_interface/prompt_renderer.rb +91 -0
- data/lib/kward/prompt_interface/question_prompt.rb +328 -0
- data/lib/kward/prompt_interface/runtime_state.rb +59 -0
- data/lib/kward/prompt_interface/screen.rb +186 -0
- data/lib/kward/prompt_interface/selection_prompt.rb +242 -0
- data/lib/kward/prompt_interface/slash_overlay.rb +102 -0
- data/lib/kward/prompt_interface/stream_state.rb +65 -0
- data/lib/kward/prompt_interface/transcript_buffer.rb +85 -0
- data/lib/kward/prompt_interface/transcript_renderer.rb +142 -0
- data/lib/kward/prompt_interface.rb +69 -1832
- data/lib/kward/prompts/commands.rb +2 -0
- data/lib/kward/prompts/templates.rb +3 -0
- data/lib/kward/prompts.rb +2 -0
- data/lib/kward/question_contract.rb +66 -0
- data/lib/kward/resources/avatar_kward_logo.rb +2 -0
- data/lib/kward/resources/pixel_logo.rb +2 -0
- data/lib/kward/rpc/attachment_normalizer.rb +60 -0
- data/lib/kward/rpc/auth_manager.rb +65 -11
- data/lib/kward/rpc/config_manager.rb +11 -0
- data/lib/kward/rpc/prompt_bridge.rb +5 -26
- data/lib/kward/rpc/redactor.rb +3 -0
- data/lib/kward/rpc/runtime_payloads.rb +4 -1
- data/lib/kward/rpc/server.rb +37 -10
- data/lib/kward/rpc/session_manager.rb +123 -347
- data/lib/kward/rpc/session_metrics.rb +68 -0
- data/lib/kward/rpc/session_tree.rb +48 -0
- data/lib/kward/rpc/session_tree_rows.rb +208 -0
- data/lib/kward/rpc/tool_event_normalizer.rb +3 -0
- data/lib/kward/rpc/tool_metadata.rb +3 -0
- data/lib/kward/rpc/transcript_normalizer.rb +3 -0
- data/lib/kward/rpc/transport.rb +3 -0
- data/lib/kward/session_diff.rb +2 -0
- data/lib/kward/session_store.rb +125 -31
- data/lib/kward/session_trash.rb +1 -0
- data/lib/kward/session_tree_renderer.rb +8 -41
- data/lib/kward/session_tree_tool_display.rb +56 -0
- data/lib/kward/skills/registry.rb +3 -0
- data/lib/kward/starter_pack_installer.rb +1 -0
- data/lib/kward/steering.rb +2 -0
- data/lib/kward/telemetry/logger.rb +3 -0
- data/lib/kward/telemetry/stats.rb +3 -0
- data/lib/kward/tools/ask_user_question.rb +20 -32
- data/lib/kward/tools/base.rb +8 -0
- data/lib/kward/tools/code_search.rb +5 -0
- data/lib/kward/tools/edit_file.rb +5 -0
- data/lib/kward/tools/list_directory.rb +5 -0
- data/lib/kward/tools/read_file.rb +5 -0
- data/lib/kward/tools/read_skill.rb +5 -0
- data/lib/kward/tools/registry.rb +33 -2
- data/lib/kward/tools/run_shell_command.rb +5 -0
- data/lib/kward/tools/search/code.rb +7 -0
- data/lib/kward/tools/search/web.rb +17 -14
- data/lib/kward/tools/tool_call.rb +25 -5
- data/lib/kward/tools/web_search.rb +7 -1
- data/lib/kward/tools/write_file.rb +5 -0
- data/lib/kward/transcript_export.rb +2 -0
- data/lib/kward/version.rb +2 -1
- data/lib/kward/workspace.rb +45 -5
- metadata +43 -1
|
@@ -1,8 +1,13 @@
|
|
|
1
|
+
require_relative "../question_contract"
|
|
1
2
|
require_relative "base"
|
|
2
3
|
|
|
4
|
+
# Namespace for the Kward CLI agent runtime.
|
|
3
5
|
module Kward
|
|
6
|
+
# Model-callable tool wrappers and their argument schemas.
|
|
4
7
|
module Tools
|
|
8
|
+
# Tool wrapper for structured clarification questions to the user.
|
|
5
9
|
class AskUserQuestion < Base
|
|
10
|
+
# Builds the tool schema and stores the execution dependency.
|
|
6
11
|
def initialize(prompt:)
|
|
7
12
|
@prompt = prompt
|
|
8
13
|
super(
|
|
@@ -42,6 +47,7 @@ module Kward
|
|
|
42
47
|
)
|
|
43
48
|
end
|
|
44
49
|
|
|
50
|
+
# Executes the tool and returns model-facing output text.
|
|
45
51
|
def call(args, _conversation, cancellation: nil)
|
|
46
52
|
return "Error: ask_user_question requires interactive prompt support." unless @prompt.respond_to?(:ask_user_question)
|
|
47
53
|
|
|
@@ -67,40 +73,22 @@ module Kward
|
|
|
67
73
|
end
|
|
68
74
|
|
|
69
75
|
def validated_questions(args)
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
questions.map.with_index(1) do |question, index|
|
|
75
|
-
return "Error: question #{index} must be an object." unless question.respond_to?(:key?)
|
|
76
|
-
return "Error: question #{index} uses unsupported multiSelect." if question.key?("multiSelect") || question.key?(:multiSelect)
|
|
77
|
-
|
|
78
|
-
text = question_value(question, :question).to_s.strip
|
|
79
|
-
header = question_value(question, :header).to_s.strip
|
|
80
|
-
options = question_value(question, :options)
|
|
81
|
-
return "Error: question #{index} requires question and header." if text.empty? || header.empty?
|
|
82
|
-
return "Error: question #{index} requires 2 to 4 options." unless options.is_a?(Array) && options.length.between?(2, 4)
|
|
83
|
-
|
|
84
|
-
normalized_options = options.map.with_index(1) do |option, option_index|
|
|
85
|
-
return "Error: question #{index} option #{option_index} must be an object." unless option.respond_to?(:key?)
|
|
86
|
-
return "Error: question #{index} option #{option_index} uses unsupported preview." if option.key?("preview") || option.key?(:preview)
|
|
87
|
-
|
|
88
|
-
label = question_value(option, :label).to_s.strip
|
|
89
|
-
description = question_value(option, :description).to_s.strip
|
|
90
|
-
return "Error: question #{index} option #{option_index} requires label and description." if label.empty? || description.empty?
|
|
91
|
-
|
|
92
|
-
{ label: label, description: description }
|
|
93
|
-
end
|
|
94
|
-
|
|
95
|
-
{ question: text, header: header, options: normalized_options }
|
|
96
|
-
end
|
|
76
|
+
QuestionContract.normalize_questions(argument(args, :questions))
|
|
77
|
+
rescue ArgumentError => e
|
|
78
|
+
"Error: #{tool_error_message(e.message)}."
|
|
97
79
|
end
|
|
98
80
|
|
|
99
|
-
def
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
81
|
+
def tool_error_message(message)
|
|
82
|
+
case message
|
|
83
|
+
when "questions must be an array"
|
|
84
|
+
"ask_user_question requires questions"
|
|
85
|
+
when "ui/question requires 1-4 questions"
|
|
86
|
+
"ask_user_question requires 1 to 4 questions"
|
|
87
|
+
else
|
|
88
|
+
message.gsub("2-4", "2 to 4")
|
|
89
|
+
.gsub("multiSelect is unsupported", "uses unsupported multiSelect")
|
|
90
|
+
.gsub("preview is unsupported", "uses unsupported preview")
|
|
91
|
+
end
|
|
104
92
|
end
|
|
105
93
|
end
|
|
106
94
|
end
|
data/lib/kward/tools/base.rb
CHANGED
|
@@ -1,8 +1,13 @@
|
|
|
1
|
+
# Namespace for the Kward CLI agent runtime.
|
|
1
2
|
module Kward
|
|
3
|
+
# Model-callable tool wrappers and their argument schemas.
|
|
2
4
|
module Tools
|
|
5
|
+
# Base class for model-callable tools and their JSON schemas.
|
|
3
6
|
class Base
|
|
7
|
+
# @return [String] function name exposed to the model
|
|
4
8
|
attr_reader :name
|
|
5
9
|
|
|
10
|
+
# Creates a tool schema definition shared by all concrete tool wrappers.
|
|
6
11
|
def initialize(name, description, properties: {}, required: [])
|
|
7
12
|
@name = name
|
|
8
13
|
@description = description
|
|
@@ -10,6 +15,7 @@ module Kward
|
|
|
10
15
|
@required = required
|
|
11
16
|
end
|
|
12
17
|
|
|
18
|
+
# Returns the strict JSON schema advertised to model providers.
|
|
13
19
|
def schema
|
|
14
20
|
{
|
|
15
21
|
type: "function",
|
|
@@ -28,6 +34,7 @@ module Kward
|
|
|
28
34
|
|
|
29
35
|
private
|
|
30
36
|
|
|
37
|
+
# Reads a tool argument while accepting symbol or string keys from restored calls.
|
|
31
38
|
def argument(args, key, default = nil)
|
|
32
39
|
return args[key] if args.key?(key)
|
|
33
40
|
return args[key.to_s] if args.key?(key.to_s)
|
|
@@ -35,6 +42,7 @@ module Kward
|
|
|
35
42
|
default
|
|
36
43
|
end
|
|
37
44
|
|
|
45
|
+
# Detects successful AGENTS.md writes so callers can refresh prompt context.
|
|
38
46
|
def agents_file_changed?(workspace, path, result)
|
|
39
47
|
result.to_s.start_with?("Wrote ", "Edited ") && File.basename(path.to_s) == "AGENTS.md" && workspace.resolved_path(path) == File.join(workspace.root.to_s, "AGENTS.md")
|
|
40
48
|
rescue StandardError
|
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
require_relative "base"
|
|
2
2
|
|
|
3
|
+
# Namespace for the Kward CLI agent runtime.
|
|
3
4
|
module Kward
|
|
5
|
+
# Model-callable tool wrappers and their argument schemas.
|
|
4
6
|
module Tools
|
|
7
|
+
# Package lookup and GitHub repository cache/search implementation.
|
|
5
8
|
class CodeSearch < Base
|
|
9
|
+
# Builds the tool schema and stores the execution dependency.
|
|
6
10
|
def initialize(code_search:)
|
|
7
11
|
@code_search = code_search
|
|
8
12
|
super(
|
|
@@ -56,6 +60,7 @@ module Kward
|
|
|
56
60
|
)
|
|
57
61
|
end
|
|
58
62
|
|
|
63
|
+
# Executes the tool and returns model-facing output text.
|
|
59
64
|
def call(args, _conversation, cancellation: nil)
|
|
60
65
|
cancellation&.raise_if_cancelled!
|
|
61
66
|
@code_search.call(args)
|
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
require_relative "base"
|
|
2
2
|
|
|
3
|
+
# Namespace for the Kward CLI agent runtime.
|
|
3
4
|
module Kward
|
|
5
|
+
# Model-callable tool wrappers and their argument schemas.
|
|
4
6
|
module Tools
|
|
7
|
+
# Tool wrapper for exact block replacement edits.
|
|
5
8
|
class EditFile < Base
|
|
9
|
+
# Builds the tool schema and stores the execution dependency.
|
|
6
10
|
def initialize(workspace:)
|
|
7
11
|
@workspace = workspace
|
|
8
12
|
super(
|
|
@@ -28,6 +32,7 @@ module Kward
|
|
|
28
32
|
)
|
|
29
33
|
end
|
|
30
34
|
|
|
35
|
+
# Executes the tool and returns model-facing output text.
|
|
31
36
|
def call(args, conversation, cancellation: nil)
|
|
32
37
|
path = argument(args, :path, "")
|
|
33
38
|
edits = argument(args, :edits, [])
|
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
require_relative "base"
|
|
2
2
|
|
|
3
|
+
# Namespace for the Kward CLI agent runtime.
|
|
3
4
|
module Kward
|
|
5
|
+
# Model-callable tool wrappers and their argument schemas.
|
|
4
6
|
module Tools
|
|
7
|
+
# Tool wrapper for listing workspace directory entries.
|
|
5
8
|
class ListDirectory < Base
|
|
9
|
+
# Builds the tool schema and stores the execution dependency.
|
|
6
10
|
def initialize(workspace:)
|
|
7
11
|
@workspace = workspace
|
|
8
12
|
super(
|
|
@@ -13,6 +17,7 @@ module Kward
|
|
|
13
17
|
)
|
|
14
18
|
end
|
|
15
19
|
|
|
20
|
+
# Executes the tool and returns model-facing output text.
|
|
16
21
|
def call(args, _conversation, cancellation: nil)
|
|
17
22
|
@workspace.list_directory(argument(args, :path, "."))
|
|
18
23
|
end
|
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
require_relative "base"
|
|
2
2
|
|
|
3
|
+
# Namespace for the Kward CLI agent runtime.
|
|
3
4
|
module Kward
|
|
5
|
+
# Model-callable tool wrappers and their argument schemas.
|
|
4
6
|
module Tools
|
|
7
|
+
# Tool wrapper for bounded workspace file reads.
|
|
5
8
|
class ReadFile < Base
|
|
9
|
+
# Builds the tool schema and stores the execution dependency.
|
|
6
10
|
def initialize(workspace:)
|
|
7
11
|
@workspace = workspace
|
|
8
12
|
super(
|
|
@@ -17,6 +21,7 @@ module Kward
|
|
|
17
21
|
)
|
|
18
22
|
end
|
|
19
23
|
|
|
24
|
+
# Executes the tool and returns model-facing output text.
|
|
20
25
|
def call(args, conversation, cancellation: nil)
|
|
21
26
|
path = argument(args, :path, "")
|
|
22
27
|
offset = argument(args, :offset)
|
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
require_relative "base"
|
|
2
2
|
require_relative "../config_files"
|
|
3
3
|
|
|
4
|
+
# Namespace for the Kward CLI agent runtime.
|
|
4
5
|
module Kward
|
|
6
|
+
# Model-callable tool wrappers and their argument schemas.
|
|
5
7
|
module Tools
|
|
8
|
+
# Tool wrapper for reading configured skill instructions.
|
|
6
9
|
class ReadSkill < Base
|
|
10
|
+
# Builds the tool schema and stores the execution dependency.
|
|
7
11
|
def initialize
|
|
8
12
|
super(
|
|
9
13
|
"read_skill",
|
|
@@ -16,6 +20,7 @@ module Kward
|
|
|
16
20
|
)
|
|
17
21
|
end
|
|
18
22
|
|
|
23
|
+
# Executes the tool and returns model-facing output text.
|
|
19
24
|
def call(args, _conversation, cancellation: nil)
|
|
20
25
|
name = argument(args, :name, "")
|
|
21
26
|
path = argument(args, :path)
|
data/lib/kward/tools/registry.rb
CHANGED
|
@@ -13,15 +13,42 @@ require_relative "search/web"
|
|
|
13
13
|
require_relative "tool_call"
|
|
14
14
|
require_relative "../workspace"
|
|
15
15
|
|
|
16
|
+
# Namespace for the Kward CLI agent runtime.
|
|
16
17
|
module Kward
|
|
17
18
|
# Exposes local workspace, search, skill, and interaction tools to the model
|
|
18
|
-
# and dispatches
|
|
19
|
+
# and dispatches tool calls into the active conversation.
|
|
20
|
+
#
|
|
21
|
+
# `ToolRegistry` is the boundary between model-requested function calls and
|
|
22
|
+
# Ruby tool objects. It owns schema exposure and transcript persistence for
|
|
23
|
+
# tool results; individual tools own validation and side effects. Keep frontend
|
|
24
|
+
# policy outside this class by passing dependencies such as `workspace` and
|
|
25
|
+
# `prompt` from CLI or RPC setup.
|
|
26
|
+
#
|
|
27
|
+
# A tool may exist in `@tools` but not be advertised in `schemas`. This allows
|
|
28
|
+
# restored transcripts or compatibility callers to dispatch known tools while
|
|
29
|
+
# config and frontend capability checks decide what the model can request next.
|
|
30
|
+
#
|
|
31
|
+
# Tool schemas are the strict output contract advertised to models and clients.
|
|
32
|
+
# Incoming calls are intentionally more tolerant: extra fields are ignored by
|
|
33
|
+
# individual tools, and legacy-compatible shapes are accepted where already
|
|
34
|
+
# supported. Required fields and invalid required values should still return
|
|
35
|
+
# explicit tool errors.
|
|
19
36
|
class ToolRegistry
|
|
20
37
|
# Tool schemas advertised to the model for the current frontend and config.
|
|
21
38
|
#
|
|
22
|
-
# @return [Array<Hash>]
|
|
39
|
+
# @return [Array<Hash>] tool schemas currently advertised to the model
|
|
23
40
|
attr_reader :schemas
|
|
24
41
|
|
|
42
|
+
# Builds tool objects and the schema list for the current frontend/config.
|
|
43
|
+
#
|
|
44
|
+
# @param workspace [Workspace] filesystem/shell boundary used by local tools
|
|
45
|
+
# @param prompt [Object, nil] interactive prompt bridge; must implement
|
|
46
|
+
# `ask_user_question` before that tool is advertised
|
|
47
|
+
# @param web_search [WebSearch] live web search implementation
|
|
48
|
+
# @param code_search [CodeSearch] public source/package search implementation
|
|
49
|
+
# @param web_search_enabled [Boolean, nil] override for web search exposure
|
|
50
|
+
# @param skills [Array<ConfigFiles::Skill>, nil] override discovered skills
|
|
51
|
+
# @param ask_user_question_enabled [Boolean, nil] override question exposure
|
|
25
52
|
def initialize(workspace: Workspace.new, prompt: nil, web_search: WebSearch.new, code_search: CodeSearch.new, web_search_enabled: nil, skills: nil, ask_user_question_enabled: nil)
|
|
26
53
|
@workspace = workspace
|
|
27
54
|
@prompt = prompt
|
|
@@ -37,6 +64,10 @@ module Kward
|
|
|
37
64
|
# Executes a model-requested tool call and appends the result to the
|
|
38
65
|
# conversation transcript.
|
|
39
66
|
#
|
|
67
|
+
# Unknown tools are recorded as tool results instead of raising. That keeps
|
|
68
|
+
# the conversation valid for the model and lets the assistant recover by
|
|
69
|
+
# choosing an advertised tool on the next turn.
|
|
70
|
+
#
|
|
40
71
|
# @param tool_call [Hash] model tool call payload
|
|
41
72
|
# @param conversation [Conversation] active conversation
|
|
42
73
|
# @return [String] tool output content appended to the conversation
|
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
require_relative "base"
|
|
2
2
|
require_relative "../workspace"
|
|
3
3
|
|
|
4
|
+
# Namespace for the Kward CLI agent runtime.
|
|
4
5
|
module Kward
|
|
6
|
+
# Model-callable tool wrappers and their argument schemas.
|
|
5
7
|
module Tools
|
|
8
|
+
# Tool wrapper for bounded shell commands in the workspace.
|
|
6
9
|
class RunShellCommand < Base
|
|
10
|
+
# Builds the tool schema and stores the execution dependency.
|
|
7
11
|
def initialize(workspace:)
|
|
8
12
|
@workspace = workspace
|
|
9
13
|
super(
|
|
@@ -17,6 +21,7 @@ module Kward
|
|
|
17
21
|
)
|
|
18
22
|
end
|
|
19
23
|
|
|
24
|
+
# Executes the tool and returns model-facing output text.
|
|
20
25
|
def call(args, _conversation, cancellation: nil)
|
|
21
26
|
command = argument(args, :command, "")
|
|
22
27
|
timeout_seconds = argument(args, :timeout_seconds, Workspace::DEFAULT_COMMAND_TIMEOUT_SECONDS)
|
|
@@ -7,7 +7,9 @@ require "pathname"
|
|
|
7
7
|
require "uri"
|
|
8
8
|
require_relative "../../config_files"
|
|
9
9
|
|
|
10
|
+
# Namespace for the Kward CLI agent runtime.
|
|
10
11
|
module Kward
|
|
12
|
+
# Package lookup and GitHub repository cache/search implementation.
|
|
11
13
|
class CodeSearch
|
|
12
14
|
DEFAULT_MAX_RESULTS = 10
|
|
13
15
|
MAX_MAX_RESULTS = 50
|
|
@@ -21,6 +23,7 @@ module Kward
|
|
|
21
23
|
ECOSYSTEMS = %w[rubygems npm pypi crates go].freeze
|
|
22
24
|
ACTIONS = %w[package_search github_search repo_clone repo_search repo_read list_cache refresh_cache clear_cache].freeze
|
|
23
25
|
|
|
26
|
+
# Creates an object for code search and repository cache operations.
|
|
24
27
|
def initialize(cache_root: nil, http_client: NetHttpClient.new, git_runner: GitRunner.new, max_output_bytes: MAX_OUTPUT_BYTES)
|
|
25
28
|
@cache_root = File.expand_path(cache_root || ConfigFiles.code_search_cache_dir)
|
|
26
29
|
@http_client = http_client
|
|
@@ -46,6 +49,7 @@ module Kward
|
|
|
46
49
|
"Error: code_search failed: #{redact(e.message)}"
|
|
47
50
|
end
|
|
48
51
|
|
|
52
|
+
# HTTP adapter used by code search registry/package lookups.
|
|
49
53
|
class NetHttpClient
|
|
50
54
|
def get_json(url, headers: {})
|
|
51
55
|
JSON.parse(get_text(url, headers: headers.merge("Accept" => "application/json")))
|
|
@@ -65,6 +69,7 @@ module Kward
|
|
|
65
69
|
end
|
|
66
70
|
end
|
|
67
71
|
|
|
72
|
+
# Git command adapter used by repository cache operations.
|
|
68
73
|
class GitRunner
|
|
69
74
|
def run(*args, chdir: nil)
|
|
70
75
|
command = ["git", *args]
|
|
@@ -410,6 +415,7 @@ module Kward
|
|
|
410
415
|
text[%r{https://github\.com/[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+(?:\.git)?}]
|
|
411
416
|
end
|
|
412
417
|
|
|
418
|
+
# Returns GitHub API headers, including an optional token when configured.
|
|
413
419
|
def github_headers
|
|
414
420
|
token = ENV["GITHUB_TOKEN"] || ENV["GH_TOKEN"]
|
|
415
421
|
token.to_s.empty? ? {} : { "Authorization" => "Bearer #{token}" }
|
|
@@ -422,6 +428,7 @@ module Kward
|
|
|
422
428
|
nil
|
|
423
429
|
end
|
|
424
430
|
|
|
431
|
+
# Returns a cache file path relative to the cloned repository root.
|
|
425
432
|
def relative_path(root, path)
|
|
426
433
|
Pathname.new(path).relative_path_from(Pathname.new(root)).to_s
|
|
427
434
|
end
|
|
@@ -5,7 +5,9 @@ require "nokogiri"
|
|
|
5
5
|
require "uri"
|
|
6
6
|
require_relative "../../config_files"
|
|
7
7
|
|
|
8
|
+
# Namespace for the Kward CLI agent runtime.
|
|
8
9
|
module Kward
|
|
10
|
+
# Live web-search implementation with provider fallbacks.
|
|
9
11
|
class WebSearch
|
|
10
12
|
DEFAULT_MAX_RESULTS = 5
|
|
11
13
|
MAX_MAX_RESULTS = 20
|
|
@@ -27,11 +29,12 @@ module Kward
|
|
|
27
29
|
"https://search.inetol.net",
|
|
28
30
|
"https://searx.tiekoetter.com"
|
|
29
31
|
].freeze
|
|
30
|
-
PROVIDERS = %w[auto exa perplexity gemini
|
|
32
|
+
PROVIDERS = %w[auto exa perplexity gemini duckduckgo].freeze
|
|
31
33
|
|
|
32
34
|
Result = Struct.new(:title, :url, :excerpt, :provider, keyword_init: true)
|
|
33
35
|
SearchResponse = Struct.new(:answer, :results, :provider, :note, keyword_init: true)
|
|
34
36
|
|
|
37
|
+
# Creates an object for web search provider operations.
|
|
35
38
|
def initialize(http_client: NetHttpClient.new, searxng_instances: PUBLIC_SEARXNG_INSTANCES, max_output_bytes: MAX_OUTPUT_BYTES, config: nil)
|
|
36
39
|
@http_client = http_client
|
|
37
40
|
@searxng_instances = searxng_instances
|
|
@@ -92,8 +95,8 @@ module Kward
|
|
|
92
95
|
perplexity_search(query, options)
|
|
93
96
|
when "gemini"
|
|
94
97
|
gemini_search(query, options)
|
|
95
|
-
when "
|
|
96
|
-
|
|
98
|
+
when "duckduckgo"
|
|
99
|
+
duckduckgo_provider_search(query, options)
|
|
97
100
|
end
|
|
98
101
|
return [response, errors.empty? ? nil : errors.join("; ")] if successful_response?(response)
|
|
99
102
|
errors << "#{provider}: no results"
|
|
@@ -105,6 +108,7 @@ module Kward
|
|
|
105
108
|
[nil, errors.join("; ")]
|
|
106
109
|
end
|
|
107
110
|
|
|
111
|
+
# Returns the configured provider fallback order for a query.
|
|
108
112
|
def provider_order(provider)
|
|
109
113
|
case provider
|
|
110
114
|
when "auto"
|
|
@@ -113,10 +117,10 @@ module Kward
|
|
|
113
117
|
order << "perplexity" if api_key("perplexity")
|
|
114
118
|
order << "gemini" if api_key("gemini")
|
|
115
119
|
end
|
|
116
|
-
order << "
|
|
120
|
+
order << "duckduckgo"
|
|
117
121
|
order
|
|
118
122
|
when "duckduckgo"
|
|
119
|
-
["
|
|
123
|
+
["duckduckgo"]
|
|
120
124
|
else
|
|
121
125
|
[provider]
|
|
122
126
|
end
|
|
@@ -352,15 +356,15 @@ module Kward
|
|
|
352
356
|
SearchResponse.new(answer: answer, results: results, provider: "gemini")
|
|
353
357
|
end
|
|
354
358
|
|
|
355
|
-
def
|
|
356
|
-
|
|
357
|
-
results, error =
|
|
359
|
+
def duckduckgo_provider_search(query, options)
|
|
360
|
+
duckduckgo_query = query_with_domain_filter(query, options[:domain_filter])
|
|
361
|
+
results, error = duckduckgo_provider_query(duckduckgo_query, options[:max_results], options[:recency_filter])
|
|
358
362
|
raise error if results.empty? && error
|
|
359
363
|
|
|
360
|
-
SearchResponse.new(answer: "", results: results, provider: results.first&.provider || "
|
|
364
|
+
SearchResponse.new(answer: "", results: results, provider: results.first&.provider || "duckduckgo", note: error)
|
|
361
365
|
end
|
|
362
366
|
|
|
363
|
-
def
|
|
367
|
+
def duckduckgo_provider_query(query, max_results, recency_filter)
|
|
364
368
|
begin
|
|
365
369
|
duckduckgo_results = duckduckgo_search(query, max_results, recency_filter)
|
|
366
370
|
return [duckduckgo_results, nil] unless duckduckgo_results.empty?
|
|
@@ -576,6 +580,7 @@ module Kward
|
|
|
576
580
|
text
|
|
577
581
|
end
|
|
578
582
|
|
|
583
|
+
# Returns browser-like headers used for HTML search fallbacks.
|
|
579
584
|
def browser_headers(accept)
|
|
580
585
|
{
|
|
581
586
|
"Accept" => accept,
|
|
@@ -604,19 +609,16 @@ module Kward
|
|
|
604
609
|
end
|
|
605
610
|
|
|
606
611
|
def web_config
|
|
607
|
-
|
|
608
|
-
value.is_a?(Hash) ? value : {}
|
|
612
|
+
ConfigFiles.web_search_config(config)
|
|
609
613
|
end
|
|
610
614
|
|
|
611
615
|
def config_value(key)
|
|
612
616
|
snake = key.to_s
|
|
613
617
|
camel = snake.gsub(/_([a-z])/) { Regexp.last_match(1).upcase }
|
|
614
618
|
prefixed = "web_search_#{snake}"
|
|
615
|
-
legacy_prefixed = "web_research_#{snake}"
|
|
616
619
|
return web_config[snake] if web_config.key?(snake)
|
|
617
620
|
return web_config[camel] if web_config.key?(camel)
|
|
618
621
|
return config[prefixed] if config.key?(prefixed)
|
|
619
|
-
return config[legacy_prefixed] if config.key?(legacy_prefixed)
|
|
620
622
|
return config[snake] if config.key?(snake)
|
|
621
623
|
return config[camel] if config.key?(camel)
|
|
622
624
|
|
|
@@ -711,6 +713,7 @@ module Kward
|
|
|
711
713
|
{ "day" => "d", "week" => "w", "month" => "m", "year" => "y" }[filter]
|
|
712
714
|
end
|
|
713
715
|
|
|
716
|
+
# HTTP adapter used by web-search providers and fallbacks.
|
|
714
717
|
class NetHttpClient
|
|
715
718
|
Response = Struct.new(:code, :body, keyword_init: true)
|
|
716
719
|
|
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
require "json"
|
|
2
|
+
require_relative "../message_access"
|
|
2
3
|
|
|
4
|
+
# Namespace for the Kward CLI agent runtime.
|
|
3
5
|
module Kward
|
|
6
|
+
# Reads and normalizes model tool-call hashes.
|
|
7
|
+
#
|
|
8
|
+
# Tool calls arrive from several providers and may be restored from session
|
|
9
|
+
# files. This module keeps provider/string/symbol compatibility in one place
|
|
10
|
+
# and exposes small helpers used by the agent loop, tool registry, transcript
|
|
11
|
+
# formatters, and RPC event normalizers.
|
|
4
12
|
module ToolCall
|
|
5
13
|
TOOL_NAME_MAP = {
|
|
6
14
|
"read_file" => "read",
|
|
@@ -8,6 +16,7 @@ module Kward
|
|
|
8
16
|
"write_file" => "write",
|
|
9
17
|
"run_shell_command" => "bash",
|
|
10
18
|
"list_directory" => "list_directory",
|
|
19
|
+
"code_search" => "code_search",
|
|
11
20
|
"web_search" => "web_search",
|
|
12
21
|
"read_skill" => "read_skill",
|
|
13
22
|
"ask_user_question" => "ask_user_question"
|
|
@@ -15,19 +24,27 @@ module Kward
|
|
|
15
24
|
|
|
16
25
|
module_function
|
|
17
26
|
|
|
27
|
+
# @return [String, nil] provider tool-call id
|
|
18
28
|
def id(tool_call)
|
|
19
29
|
value(tool_call, :id)
|
|
20
30
|
end
|
|
21
31
|
|
|
32
|
+
# @return [String, nil] requested tool/function name
|
|
22
33
|
def name(tool_call)
|
|
23
34
|
value(function(tool_call), :name)
|
|
24
35
|
end
|
|
25
36
|
|
|
37
|
+
# Returns the short name used in compact UI labels.
|
|
38
|
+
#
|
|
39
|
+
# @return [String] display label such as `read`, `edit`, or `bash`
|
|
26
40
|
def display_name(tool_call)
|
|
27
41
|
raw_name = name(tool_call)
|
|
28
42
|
normalized_name(raw_name) || raw_name || "unknown_tool"
|
|
29
43
|
end
|
|
30
44
|
|
|
45
|
+
# Parses the requested tool arguments.
|
|
46
|
+
#
|
|
47
|
+
# @return [Hash] decoded argument object, or an empty hash for invalid JSON
|
|
31
48
|
def arguments(tool_call)
|
|
32
49
|
parse_arguments(raw_arguments(tool_call))
|
|
33
50
|
end
|
|
@@ -44,6 +61,10 @@ module Kward
|
|
|
44
61
|
TOOL_NAME_MAP[name.to_s]
|
|
45
62
|
end
|
|
46
63
|
|
|
64
|
+
# Converts provider argument payloads into hashes.
|
|
65
|
+
#
|
|
66
|
+
# Providers normally send JSON strings, while tests and compatibility callers
|
|
67
|
+
# may pass hashes directly.
|
|
47
68
|
def parse_arguments(arguments)
|
|
48
69
|
return {} if arguments.nil? || (arguments.respond_to?(:empty?) && arguments.empty?)
|
|
49
70
|
return arguments if arguments.is_a?(Hash)
|
|
@@ -53,6 +74,9 @@ module Kward
|
|
|
53
74
|
{}
|
|
54
75
|
end
|
|
55
76
|
|
|
77
|
+
# Recursively converts snake_case hash keys to camelCase symbols.
|
|
78
|
+
#
|
|
79
|
+
# @return [Hash] camelized copy of `args`
|
|
56
80
|
def camelize_args(args)
|
|
57
81
|
return {} unless args.is_a?(Hash)
|
|
58
82
|
|
|
@@ -62,11 +86,7 @@ module Kward
|
|
|
62
86
|
end
|
|
63
87
|
|
|
64
88
|
def value(object, key)
|
|
65
|
-
|
|
66
|
-
return object[key] if object.key?(key)
|
|
67
|
-
return object[key.to_s] if object.key?(key.to_s)
|
|
68
|
-
|
|
69
|
-
nil
|
|
89
|
+
MessageAccess.value(object, key)
|
|
70
90
|
end
|
|
71
91
|
|
|
72
92
|
def camelize_value(item)
|
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
require_relative "base"
|
|
2
|
+
require_relative "search/web"
|
|
2
3
|
|
|
4
|
+
# Namespace for the Kward CLI agent runtime.
|
|
3
5
|
module Kward
|
|
6
|
+
# Model-callable tool wrappers and their argument schemas.
|
|
4
7
|
module Tools
|
|
8
|
+
# Live web-search implementation with provider fallbacks.
|
|
5
9
|
class WebSearch < Base
|
|
10
|
+
# Builds the tool schema and stores the execution dependency.
|
|
6
11
|
def initialize(web_search:)
|
|
7
12
|
@web_search = web_search
|
|
8
13
|
super(
|
|
@@ -22,7 +27,7 @@ module Kward
|
|
|
22
27
|
},
|
|
23
28
|
provider: {
|
|
24
29
|
type: "string",
|
|
25
|
-
enum:
|
|
30
|
+
enum: Kward::WebSearch::PROVIDERS,
|
|
26
31
|
description: "Provider override; default auto."
|
|
27
32
|
},
|
|
28
33
|
recency_filter: {
|
|
@@ -40,6 +45,7 @@ module Kward
|
|
|
40
45
|
)
|
|
41
46
|
end
|
|
42
47
|
|
|
48
|
+
# Executes the tool and returns model-facing output text.
|
|
43
49
|
def call(args, _conversation, cancellation: nil)
|
|
44
50
|
@web_search.search(args)
|
|
45
51
|
end
|
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
require_relative "base"
|
|
2
2
|
|
|
3
|
+
# Namespace for the Kward CLI agent runtime.
|
|
3
4
|
module Kward
|
|
5
|
+
# Model-callable tool wrappers and their argument schemas.
|
|
4
6
|
module Tools
|
|
7
|
+
# Tool wrapper for guarded full-file writes.
|
|
5
8
|
class WriteFile < Base
|
|
9
|
+
# Builds the tool schema and stores the execution dependency.
|
|
6
10
|
def initialize(workspace:)
|
|
7
11
|
@workspace = workspace
|
|
8
12
|
super(
|
|
@@ -16,6 +20,7 @@ module Kward
|
|
|
16
20
|
)
|
|
17
21
|
end
|
|
18
22
|
|
|
23
|
+
# Executes the tool and returns model-facing output text.
|
|
19
24
|
def call(args, conversation, cancellation: nil)
|
|
20
25
|
path = argument(args, :path, "")
|
|
21
26
|
content = argument(args, :content, "")
|
data/lib/kward/version.rb
CHANGED