kward 0.74.0 → 0.75.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 +35 -0
- data/Gemfile.lock +2 -2
- data/README.md +4 -2
- data/Rakefile +18 -0
- data/doc/configuration.md +56 -3
- data/doc/extensibility.md +6 -19
- data/doc/getting-started.md +1 -1
- data/doc/lifecycle-hooks.md +424 -0
- data/doc/memory.md +1 -1
- data/doc/personas.md +2 -2
- data/doc/plugins.md +1 -1
- data/doc/releasing.md +7 -15
- data/doc/rpc.md +108 -16
- data/doc/session-management.md +9 -9
- data/doc/skills.md +199 -0
- data/doc/usage.md +7 -7
- data/lib/kward/agent.rb +72 -22
- data/lib/kward/auth/anthropic_oauth.rb +5 -87
- data/lib/kward/auth/oauth_helpers.rb +105 -0
- data/lib/kward/auth/openai_oauth.rb +4 -86
- data/lib/kward/cli/commands.rb +9 -2
- data/lib/kward/cli/compaction.rb +7 -0
- data/lib/kward/cli/git.rb +30 -3
- data/lib/kward/cli/hook_commands.rb +258 -0
- data/lib/kward/cli/memory_commands.rb +1 -5
- data/lib/kward/cli/plugins.rb +41 -1
- data/lib/kward/cli/prompt_interface.rb +32 -3
- data/lib/kward/cli/rendering.rb +1 -1
- data/lib/kward/cli/runtime_helpers.rb +44 -5
- data/lib/kward/cli/sessions.rb +13 -0
- data/lib/kward/cli/settings.rb +226 -152
- data/lib/kward/cli/slash_commands.rb +81 -42
- data/lib/kward/cli/tabs.rb +26 -6
- data/lib/kward/cli.rb +26 -3
- data/lib/kward/cli_transcript_formatter.rb +0 -7
- data/lib/kward/compaction/token_estimator.rb +112 -0
- data/lib/kward/compactor.rb +42 -116
- data/lib/kward/config_files.rb +117 -25
- data/lib/kward/deep_copy.rb +42 -0
- data/lib/kward/diff_view_mode.rb +36 -0
- data/lib/kward/events.rb +1 -0
- data/lib/kward/export_path.rb +2 -6
- data/lib/kward/frontmatter.rb +43 -0
- data/lib/kward/hooks/audit_log.rb +121 -0
- data/lib/kward/hooks/catalog.rb +110 -0
- data/lib/kward/hooks/command_handler.rb +66 -0
- data/lib/kward/hooks/config_loader.rb +104 -0
- data/lib/kward/hooks/decision.rb +102 -0
- data/lib/kward/hooks/event.rb +60 -0
- data/lib/kward/hooks/http_handler.rb +72 -0
- data/lib/kward/hooks/manager.rb +158 -0
- data/lib/kward/hooks/matcher.rb +75 -0
- data/lib/kward/hooks.rb +16 -0
- data/lib/kward/markdown_transcript.rb +4 -3
- data/lib/kward/mcp/client.rb +1 -1
- data/lib/kward/mcp/server_config.rb +7 -3
- data/lib/kward/mcp/stdio_transport.rb +7 -1
- data/lib/kward/memory/turn_context.rb +19 -0
- data/lib/kward/message_access.rb +2 -2
- data/lib/kward/message_text.rb +19 -0
- data/lib/kward/model/client.rb +13 -45
- data/lib/kward/model/context_usage.rb +10 -4
- data/lib/kward/model/copilot_models.rb +44 -0
- data/lib/kward/model/model_info.rb +24 -0
- data/lib/kward/model/payloads.rb +15 -17
- data/lib/kward/model/stream_parser.rb +178 -13
- data/lib/kward/pan/server.rb +51 -2
- data/lib/kward/path_guard.rb +13 -0
- data/lib/kward/plugin_registry.rb +86 -25
- data/lib/kward/prompt_interface/editor/controller.rb +60 -1
- data/lib/kward/prompt_interface/editor/renderer.rb +11 -0
- data/lib/kward/prompt_interface/editor/state.rb +5 -1
- data/lib/kward/prompt_interface/selection_prompt.rb +26 -6
- data/lib/kward/prompt_interface.rb +4 -1
- data/lib/kward/prompts/commands.rb +3 -3
- data/lib/kward/prompts.rb +7 -7
- data/lib/kward/rpc/mcp_status.rb +50 -0
- data/lib/kward/rpc/prompt_bridge.rb +38 -4
- data/lib/kward/rpc/runtime_payloads.rb +1 -1
- data/lib/kward/rpc/server.rb +94 -14
- data/lib/kward/rpc/session_manager.rb +258 -33
- data/lib/kward/rpc/session_metrics.rb +1 -1
- data/lib/kward/rpc/session_tree_rows.rb +13 -64
- data/lib/kward/rpc/tool_event_normalizer.rb +16 -2
- data/lib/kward/rpc/transcript_normalizer.rb +9 -10
- data/lib/kward/rpc/turn_context.rb +109 -0
- data/lib/kward/session_store.rb +6 -20
- data/lib/kward/session_tree_nodes.rb +98 -18
- data/lib/kward/session_tree_renderer.rb +6 -61
- data/lib/kward/skills/registry.rb +109 -17
- data/lib/kward/tools/mcp_tool.rb +4 -0
- data/lib/kward/tools/read_skill.rb +9 -4
- data/lib/kward/tools/registry.rb +266 -8
- data/lib/kward/tools/search/code.rb +4 -22
- data/lib/kward/tools/search/web.rb +28 -2
- data/lib/kward/update_check.rb +117 -0
- data/lib/kward/version.rb +1 -1
- data/lib/kward/workers/manager.rb +43 -3
- data/lib/kward/workspace.rb +3 -1
- data/templates/default/fulldoc/html/css/kward.css +22 -0
- data/templates/default/fulldoc/html/js/kward.js +2 -0
- data/templates/default/kward_navigation.rb +4 -2
- data/templates/default/layout/html/setup.rb +2 -0
- metadata +25 -1
|
@@ -11,13 +11,17 @@ module Kward
|
|
|
11
11
|
module_function
|
|
12
12
|
|
|
13
13
|
def clients_from_config(config)
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
servers.filter_map do |name, settings|
|
|
14
|
+
configured_servers(config).filter_map do |name, settings|
|
|
17
15
|
client_from_settings(name, settings)
|
|
18
16
|
end
|
|
19
17
|
end
|
|
20
18
|
|
|
19
|
+
def configured_servers(config)
|
|
20
|
+
ConfigFiles.mcp_servers(config).select do |_name, settings|
|
|
21
|
+
settings.is_a?(Hash) && settings["enabled"] != false
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
21
25
|
def client_from_settings(name, settings)
|
|
22
26
|
return nil unless settings.is_a?(Hash)
|
|
23
27
|
return nil if settings["enabled"] == false
|
|
@@ -10,6 +10,8 @@ module Kward
|
|
|
10
10
|
class StdioTransport
|
|
11
11
|
DEFAULT_TIMEOUT_SECONDS = 10
|
|
12
12
|
|
|
13
|
+
attr_reader :command
|
|
14
|
+
|
|
13
15
|
def initialize(command:, args: [], env: {}, timeout_seconds: DEFAULT_TIMEOUT_SECONDS)
|
|
14
16
|
@command = command.to_s
|
|
15
17
|
@args = Array(args).map(&:to_s)
|
|
@@ -62,7 +64,11 @@ module Kward
|
|
|
62
64
|
raise ArgumentError, "MCP server command is required" if @command.empty?
|
|
63
65
|
|
|
64
66
|
@stdin, @stdout, @stderr, @wait_thread = Open3.popen3(@env, @command, *@args)
|
|
65
|
-
@stderr_thread = Thread.new
|
|
67
|
+
@stderr_thread = Thread.new do
|
|
68
|
+
@stderr.each_line { |_line| }
|
|
69
|
+
rescue IOError
|
|
70
|
+
nil
|
|
71
|
+
end
|
|
66
72
|
@started = true
|
|
67
73
|
rescue SystemCallError => e
|
|
68
74
|
raise "Failed to start MCP server #{@command}: #{e.message}"
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
require_relative "manager"
|
|
2
|
+
|
|
3
|
+
# Namespace for the Kward CLI agent runtime.
|
|
4
|
+
module Kward
|
|
5
|
+
module Memory
|
|
6
|
+
# Applies retrieved memory context to a conversation before a model turn.
|
|
7
|
+
module TurnContext
|
|
8
|
+
module_function
|
|
9
|
+
|
|
10
|
+
def apply(conversation:, input:, manager: Manager.new)
|
|
11
|
+
retrieval = manager.retrieve_relevant(input: input, workspace_root: conversation.workspace_root)
|
|
12
|
+
conversation.last_memory_retrieval = retrieval
|
|
13
|
+
conversation.memory_context = manager.memory_block(retrieval)
|
|
14
|
+
conversation.refresh_system_message!
|
|
15
|
+
retrieval
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
data/lib/kward/message_access.rb
CHANGED
|
@@ -5,8 +5,8 @@ module Kward
|
|
|
5
5
|
# Kward stores transcript entries as plain hashes because model payloads,
|
|
6
6
|
# JSONL sessions, plugins, and RPC normalizers all need to pass them around
|
|
7
7
|
# without framework objects. Restored sessions may contain either symbol keys,
|
|
8
|
-
# string keys, or
|
|
9
|
-
#
|
|
8
|
+
# string keys, or camelCase aliases. `MessageAccess` centralizes those lookup
|
|
9
|
+
# rules so callers do not grow one-off compatibility branches.
|
|
10
10
|
module MessageAccess
|
|
11
11
|
module_function
|
|
12
12
|
|
data/lib/kward/message_text.rb
CHANGED
|
@@ -28,6 +28,20 @@ module Kward
|
|
|
28
28
|
content_text(MessageAccess.content(message)).strip
|
|
29
29
|
end
|
|
30
30
|
|
|
31
|
+
# Returns compact one-line text for session tree and list previews.
|
|
32
|
+
#
|
|
33
|
+
# @param message [Hash] persisted conversation message
|
|
34
|
+
# @param length [Integer] maximum preview length
|
|
35
|
+
# @return [String] whitespace-normalized text, truncated with ellipsis
|
|
36
|
+
def preview(message, length: 120)
|
|
37
|
+
text = if MessageAccess.role(message) == "compactionSummary"
|
|
38
|
+
MessageAccess.summary(message).to_s
|
|
39
|
+
else
|
|
40
|
+
full_text(message)
|
|
41
|
+
end
|
|
42
|
+
truncate_preview(text, length: length)
|
|
43
|
+
end
|
|
44
|
+
|
|
31
45
|
# Converts message content into plain text without applying display-content
|
|
32
46
|
# overrides.
|
|
33
47
|
#
|
|
@@ -41,5 +55,10 @@ module Kward
|
|
|
41
55
|
content.to_s
|
|
42
56
|
end
|
|
43
57
|
end
|
|
58
|
+
|
|
59
|
+
def truncate_preview(text, length: 120)
|
|
60
|
+
normalized = text.to_s.gsub(/\s+/, " ").strip
|
|
61
|
+
normalized.length > length ? "#{normalized.slice(0, length - 3)}..." : normalized
|
|
62
|
+
end
|
|
44
63
|
end
|
|
45
64
|
end
|
data/lib/kward/model/client.rb
CHANGED
|
@@ -8,6 +8,7 @@ require_relative "../cancellation"
|
|
|
8
8
|
require_relative "../config_files"
|
|
9
9
|
require_relative "../openrouter_model_cache"
|
|
10
10
|
require_relative "context_overflow"
|
|
11
|
+
require_relative "copilot_models"
|
|
11
12
|
require_relative "model_info"
|
|
12
13
|
require_relative "payloads"
|
|
13
14
|
require_relative "../telemetry/logger"
|
|
@@ -227,8 +228,8 @@ module Kward
|
|
|
227
228
|
end
|
|
228
229
|
|
|
229
230
|
# Projects messages/tools into the provider-specific context shape without sending it.
|
|
230
|
-
def current_context_parts(messages, tools)
|
|
231
|
-
build_context_parts(
|
|
231
|
+
def current_context_parts(messages, tools, provider: current_provider, model: nil)
|
|
232
|
+
build_context_parts(ModelInfo.provider_label(provider), messages, tools, model: model)
|
|
232
233
|
end
|
|
233
234
|
|
|
234
235
|
# Returns whether the active provider can accept steering while a turn is streaming.
|
|
@@ -525,30 +526,15 @@ module Kward
|
|
|
525
526
|
live_models = fetch_copilot_models
|
|
526
527
|
return static_copilot_model_choices if live_models.empty?
|
|
527
528
|
|
|
528
|
-
|
|
529
|
+
CopilotModels.supported_choices(live_models)
|
|
529
530
|
end
|
|
530
531
|
|
|
531
532
|
def static_copilot_model_choices
|
|
532
|
-
|
|
533
|
-
end
|
|
534
|
-
|
|
535
|
-
def supported_copilot_model_choices(choices)
|
|
536
|
-
choices.select { |model| copilot_supported_model?(model) }.uniq
|
|
533
|
+
CopilotModels.supported_choices(ModelInfo::COPILOT_MODEL_CHOICES)
|
|
537
534
|
end
|
|
538
535
|
|
|
539
536
|
def resolved_copilot_chat_model(configured_model)
|
|
540
|
-
|
|
541
|
-
return configured_model if choices.empty? || choices.include?(configured_model)
|
|
542
|
-
|
|
543
|
-
supported = choices.find { |model| copilot_supported_model?(model) }
|
|
544
|
-
raise "No Copilot models supported by Kward are available for this account. Kward currently supports Copilot GPT-5 Responses and Gemini/GPT-4.1 chat models." unless supported
|
|
545
|
-
|
|
546
|
-
supported
|
|
547
|
-
end
|
|
548
|
-
|
|
549
|
-
def copilot_supported_model?(model)
|
|
550
|
-
text = model.to_s
|
|
551
|
-
copilot_responses_model?(text) || text.match?(/\A(?:gemini-|gpt-4\.1|oswe-)/)
|
|
537
|
+
CopilotModels.resolved_chat_model(configured_model, fetch_copilot_models)
|
|
552
538
|
end
|
|
553
539
|
|
|
554
540
|
def fetch_copilot_models
|
|
@@ -566,33 +552,11 @@ module Kward
|
|
|
566
552
|
response = Net::HTTP.start(url.hostname, url.port, use_ssl: true) { |http| http.request(request) }
|
|
567
553
|
return [] unless response.is_a?(Net::HTTPSuccess)
|
|
568
554
|
|
|
569
|
-
@copilot_models =
|
|
555
|
+
@copilot_models = CopilotModels.parse(response.body)
|
|
570
556
|
rescue StandardError
|
|
571
557
|
[]
|
|
572
558
|
end
|
|
573
559
|
|
|
574
|
-
def parse_copilot_models(body)
|
|
575
|
-
model_catalog_entries(body).filter_map do |entry|
|
|
576
|
-
copilot_model_id(entry)
|
|
577
|
-
end.uniq
|
|
578
|
-
rescue JSON::ParserError
|
|
579
|
-
[]
|
|
580
|
-
end
|
|
581
|
-
|
|
582
|
-
def model_catalog_entries(body)
|
|
583
|
-
data = JSON.parse(body.to_s)
|
|
584
|
-
entries = data.is_a?(Hash) ? data["data"] || data["models"] || data["items"] || [] : data
|
|
585
|
-
Array(entries)
|
|
586
|
-
end
|
|
587
|
-
|
|
588
|
-
def copilot_model_id(entry)
|
|
589
|
-
return entry.to_s.strip unless entry.is_a?(Hash)
|
|
590
|
-
return nil if entry.key?("model_picker_enabled") && entry["model_picker_enabled"] == false
|
|
591
|
-
|
|
592
|
-
id = entry["id"] || entry["model"] || entry["name"]
|
|
593
|
-
id.to_s.strip unless id.to_s.strip.empty?
|
|
594
|
-
end
|
|
595
|
-
|
|
596
560
|
def copilot_responses_payload(messages, tools, max_tokens: nil, model: nil, reasoning: nil)
|
|
597
561
|
parts = build_context_parts("CopilotResponses", messages, tools, model: model)
|
|
598
562
|
payload = {
|
|
@@ -718,11 +682,11 @@ module Kward
|
|
|
718
682
|
end
|
|
719
683
|
|
|
720
684
|
def parse_codex_sse(body, on_reasoning_delta: nil, on_assistant_delta: nil)
|
|
721
|
-
ModelStreamParser.parse_codex_sse(body, on_reasoning_delta: on_reasoning_delta, on_assistant_delta: on_assistant_delta, usage_normalizer: method(:normalized_usage), request_error_class: RequestError)
|
|
685
|
+
ModelStreamParser.parse_codex_sse(body, on_reasoning_delta: on_reasoning_delta, on_assistant_delta: on_assistant_delta, show_raw_reasoning: codex_show_raw_reasoning?, usage_normalizer: method(:normalized_usage), request_error_class: RequestError)
|
|
722
686
|
end
|
|
723
687
|
|
|
724
688
|
def parse_codex_sse_stream(response, on_reasoning_delta: nil, on_assistant_delta: nil, cancellation: nil)
|
|
725
|
-
ModelStreamParser.parse_codex_sse_stream(response, on_reasoning_delta: on_reasoning_delta, on_assistant_delta: on_assistant_delta, cancellation: cancellation, usage_normalizer: method(:normalized_usage), request_error_class: RequestError)
|
|
689
|
+
ModelStreamParser.parse_codex_sse_stream(response, on_reasoning_delta: on_reasoning_delta, on_assistant_delta: on_assistant_delta, cancellation: cancellation, show_raw_reasoning: codex_show_raw_reasoning?, usage_normalizer: method(:normalized_usage), request_error_class: RequestError)
|
|
726
690
|
end
|
|
727
691
|
|
|
728
692
|
def parse_anthropic_sse_stream(response, on_reasoning_delta: nil, on_assistant_delta: nil, cancellation: nil)
|
|
@@ -871,6 +835,10 @@ module Kward
|
|
|
871
835
|
ConfigFiles.config_value(@config, *keys)
|
|
872
836
|
end
|
|
873
837
|
|
|
838
|
+
def codex_show_raw_reasoning?
|
|
839
|
+
@config["codex_show_raw_reasoning"] == true
|
|
840
|
+
end
|
|
841
|
+
|
|
874
842
|
def load_config
|
|
875
843
|
ConfigFiles.read_config(@config_path)
|
|
876
844
|
end
|
|
@@ -80,6 +80,10 @@ module Kward
|
|
|
80
80
|
|
|
81
81
|
# Structured context usage result returned to frontends.
|
|
82
82
|
class TiktokenTokenCounter
|
|
83
|
+
def initialize
|
|
84
|
+
@encodings = {}
|
|
85
|
+
end
|
|
86
|
+
|
|
83
87
|
def count(text, model:)
|
|
84
88
|
text = text.to_s
|
|
85
89
|
tokenizer = encoding(model)
|
|
@@ -93,11 +97,13 @@ module Kward
|
|
|
93
97
|
private
|
|
94
98
|
|
|
95
99
|
def encoding(model)
|
|
96
|
-
|
|
100
|
+
@encodings[model.to_s] ||= begin
|
|
101
|
+
require "tiktoken_ruby"
|
|
97
102
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
103
|
+
Tiktoken.encoding_for_model(model.to_s) || Tiktoken.get_encoding(encoding_name_for_model(model))
|
|
104
|
+
rescue StandardError
|
|
105
|
+
Tiktoken.get_encoding(encoding_name_for_model(model)) if defined?(Tiktoken)
|
|
106
|
+
end
|
|
101
107
|
end
|
|
102
108
|
|
|
103
109
|
def rough_count(text)
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
require "json"
|
|
2
|
+
|
|
3
|
+
# Namespace for the Kward CLI agent runtime.
|
|
4
|
+
module Kward
|
|
5
|
+
# Parses and filters GitHub Copilot model catalog responses.
|
|
6
|
+
module CopilotModels
|
|
7
|
+
module_function
|
|
8
|
+
|
|
9
|
+
def parse(body)
|
|
10
|
+
catalog_entries(body).filter_map { |entry| model_id(entry) }.uniq
|
|
11
|
+
rescue JSON::ParserError
|
|
12
|
+
[]
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def supported?(model)
|
|
16
|
+
text = model.to_s
|
|
17
|
+
text.match?(/\Agpt-5(?:\.|-|\z)/) || text.match?(/\A(?:gemini-|gpt-4\.1|oswe-)/)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def supported_choices(choices)
|
|
21
|
+
choices.select { |model| supported?(model) }.uniq
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def resolved_chat_model(configured_model, choices)
|
|
25
|
+
return configured_model if choices.empty? || choices.include?(configured_model)
|
|
26
|
+
|
|
27
|
+
choices.find { |model| supported?(model) } || raise("No Copilot models supported by Kward are available for this account. Kward currently supports Copilot GPT-5 Responses and Gemini/GPT-4.1 chat models.")
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def catalog_entries(body)
|
|
31
|
+
data = JSON.parse(body.to_s)
|
|
32
|
+
entries = data.is_a?(Hash) ? data["data"] || data["models"] || data["items"] || [] : data
|
|
33
|
+
Array(entries)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def model_id(entry)
|
|
37
|
+
return entry.to_s.strip unless entry.is_a?(Hash)
|
|
38
|
+
return nil if entry.key?("model_picker_enabled") && entry["model_picker_enabled"] == false
|
|
39
|
+
|
|
40
|
+
id = entry["id"] || entry["model"] || entry["name"]
|
|
41
|
+
id.to_s.strip unless id.to_s.strip.empty?
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
@@ -412,6 +412,30 @@ module Kward
|
|
|
412
412
|
}.compact
|
|
413
413
|
end
|
|
414
414
|
|
|
415
|
+
def normalize_available(models, current_provider:, current_model:, current_reasoning_effort: nil)
|
|
416
|
+
Array(models).map do |model|
|
|
417
|
+
normalize(
|
|
418
|
+
model,
|
|
419
|
+
current_provider: current_provider,
|
|
420
|
+
current_model: current_model,
|
|
421
|
+
current_reasoning_effort: current_reasoning_effort
|
|
422
|
+
)
|
|
423
|
+
end
|
|
424
|
+
end
|
|
425
|
+
|
|
426
|
+
def current_payload(provider:, model:, reasoning_effort: nil, context_window: nil)
|
|
427
|
+
normalize(
|
|
428
|
+
{
|
|
429
|
+
provider: provider,
|
|
430
|
+
id: model,
|
|
431
|
+
model: model,
|
|
432
|
+
reasoningEffort: reasoning_effort,
|
|
433
|
+
contextWindow: context_window,
|
|
434
|
+
current: true
|
|
435
|
+
}
|
|
436
|
+
)
|
|
437
|
+
end
|
|
438
|
+
|
|
415
439
|
def boolean_value(value, default: false)
|
|
416
440
|
return default if value.nil?
|
|
417
441
|
return value if value == true || value == false
|
data/lib/kward/model/payloads.rb
CHANGED
|
@@ -188,14 +188,12 @@ module Kward
|
|
|
188
188
|
text = plain_content(content).to_s
|
|
189
189
|
blocks << { type: "text", text: text } unless text.empty?
|
|
190
190
|
MessageAccess.tool_calls(message).each do |tool_call|
|
|
191
|
-
|
|
192
|
-
name = function[:name] || function["name"]
|
|
193
|
-
arguments = function[:arguments] || function["arguments"] || "{}"
|
|
191
|
+
name = ToolCall.name(tool_call)
|
|
194
192
|
blocks << {
|
|
195
193
|
type: "tool_use",
|
|
196
|
-
id: normalize_anthropic_tool_call_id(
|
|
194
|
+
id: normalize_anthropic_tool_call_id(ToolCall.id(tool_call) || "call_#{name}"),
|
|
197
195
|
name: claude_code_tool_name(name),
|
|
198
|
-
input: parse_tool_arguments(
|
|
196
|
+
input: parse_tool_arguments(ToolCall.raw_arguments(tool_call) || "{}")
|
|
199
197
|
}
|
|
200
198
|
end
|
|
201
199
|
output << { role: "assistant", content: blocks } unless blocks.empty?
|
|
@@ -260,11 +258,11 @@ module Kward
|
|
|
260
258
|
end
|
|
261
259
|
|
|
262
260
|
def anthropic_tool_schema(tool)
|
|
263
|
-
function =
|
|
264
|
-
schema = function
|
|
261
|
+
function = ToolCall.function(tool)
|
|
262
|
+
schema = ToolCall.value(function, :parameters) || {}
|
|
265
263
|
{
|
|
266
|
-
name: claude_code_tool_name(function
|
|
267
|
-
description: function
|
|
264
|
+
name: claude_code_tool_name(ToolCall.value(function, :name)),
|
|
265
|
+
description: ToolCall.value(function, :description) || "",
|
|
268
266
|
input_schema: {
|
|
269
267
|
type: "object",
|
|
270
268
|
properties: schema[:properties] || schema["properties"] || {},
|
|
@@ -296,12 +294,12 @@ module Kward
|
|
|
296
294
|
content = plain_content(content)
|
|
297
295
|
input << codex_message("assistant", content.to_s) unless content.to_s.empty?
|
|
298
296
|
MessageAccess.tool_calls(message).each do |tool_call|
|
|
299
|
-
|
|
297
|
+
name = ToolCall.name(tool_call)
|
|
300
298
|
input << {
|
|
301
299
|
type: "function_call",
|
|
302
|
-
call_id:
|
|
303
|
-
name:
|
|
304
|
-
arguments:
|
|
300
|
+
call_id: ToolCall.id(tool_call) || name || "tool-call",
|
|
301
|
+
name: name,
|
|
302
|
+
arguments: ToolCall.raw_arguments(tool_call) || "{}"
|
|
305
303
|
}
|
|
306
304
|
end
|
|
307
305
|
else
|
|
@@ -420,12 +418,12 @@ module Kward
|
|
|
420
418
|
end
|
|
421
419
|
|
|
422
420
|
def codex_tool_schema(tool)
|
|
423
|
-
function =
|
|
421
|
+
function = ToolCall.function(tool)
|
|
424
422
|
{
|
|
425
423
|
type: "function",
|
|
426
|
-
name: function
|
|
427
|
-
description: function
|
|
428
|
-
parameters: function
|
|
424
|
+
name: ToolCall.value(function, :name),
|
|
425
|
+
description: ToolCall.value(function, :description) || "",
|
|
426
|
+
parameters: ToolCall.value(function, :parameters) || {},
|
|
429
427
|
strict: false
|
|
430
428
|
}
|
|
431
429
|
end
|