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
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
require_relative "../message_access"
|
|
2
|
+
|
|
3
|
+
# Namespace for the Kward CLI agent runtime.
|
|
4
|
+
module Kward
|
|
5
|
+
# JSON-RPC backend namespace used by UI clients.
|
|
6
|
+
module RPC
|
|
7
|
+
# Computes conversation metrics and context usage for RPC runtime payloads.
|
|
8
|
+
class SessionMetrics
|
|
9
|
+
def initialize(context_usage:)
|
|
10
|
+
@context_usage = context_usage
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def message_count(conversation)
|
|
14
|
+
conversation.messages.count { |message| MessageAccess.role(message) != "system" }
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def message_stats(conversation)
|
|
18
|
+
conversation.messages.each_with_object(default_message_stats) do |message, counts|
|
|
19
|
+
role = MessageAccess.role(message)
|
|
20
|
+
next if role == "system"
|
|
21
|
+
|
|
22
|
+
counts[:totalMessages] += 1
|
|
23
|
+
case role
|
|
24
|
+
when "user"
|
|
25
|
+
counts[:userMessages] += 1
|
|
26
|
+
when "assistant"
|
|
27
|
+
counts[:assistantMessages] += 1
|
|
28
|
+
counts[:toolCalls] += MessageAccess.tool_calls(message).length
|
|
29
|
+
when "tool", "toolResult"
|
|
30
|
+
counts[:toolResults] += 1
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def context_usage(rpc_session, model, client:)
|
|
36
|
+
context_parts = if client.respond_to?(:current_context_parts)
|
|
37
|
+
client.current_context_parts(rpc_session.conversation.messages, rpc_session.tool_registry.schemas)
|
|
38
|
+
else
|
|
39
|
+
{
|
|
40
|
+
provider: model[:provider],
|
|
41
|
+
model: model[:id],
|
|
42
|
+
messages: rpc_session.conversation.messages,
|
|
43
|
+
tools: rpc_session.tool_registry.schemas
|
|
44
|
+
}
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
@context_usage.call(
|
|
48
|
+
provider: model[:provider],
|
|
49
|
+
model: model[:id],
|
|
50
|
+
context_window: model[:contextWindow],
|
|
51
|
+
context_parts: context_parts
|
|
52
|
+
)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
private
|
|
56
|
+
|
|
57
|
+
def default_message_stats
|
|
58
|
+
{
|
|
59
|
+
userMessages: 0,
|
|
60
|
+
assistantMessages: 0,
|
|
61
|
+
toolCalls: 0,
|
|
62
|
+
toolResults: 0,
|
|
63
|
+
totalMessages: 0
|
|
64
|
+
}
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
require_relative "../message_access"
|
|
2
|
+
|
|
3
|
+
# Namespace for the Kward CLI agent runtime.
|
|
4
|
+
module Kward
|
|
5
|
+
# JSON-RPC backend namespace used by UI clients.
|
|
6
|
+
module RPC
|
|
7
|
+
# Helper for resolving and selecting persisted session tree entries.
|
|
8
|
+
class SessionTree
|
|
9
|
+
def initialize(rpc_session)
|
|
10
|
+
@rpc_session = rpc_session
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def entries
|
|
14
|
+
@rpc_session.store.session_entries(@rpc_session.session.path)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def resolve_entry_id(entry_id, entries: self.entries)
|
|
18
|
+
id = entry_id.to_s
|
|
19
|
+
return id if entries.any? { |record| record["id"].to_s == id }
|
|
20
|
+
|
|
21
|
+
match = id.match(/\Amessage:(\d+)\z/)
|
|
22
|
+
return entries[match[1].to_i]&.dig("id") if match
|
|
23
|
+
|
|
24
|
+
id
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def active_path_ids(entries, leaf_id)
|
|
28
|
+
by_id = entries.to_h { |entry| [entry["id"].to_s, entry] }
|
|
29
|
+
ids = []
|
|
30
|
+
current = by_id[leaf_id.to_s]
|
|
31
|
+
while current
|
|
32
|
+
ids << current["id"].to_s
|
|
33
|
+
current = by_id[current["parentId"].to_s]
|
|
34
|
+
end
|
|
35
|
+
ids
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def user_entry?(entry)
|
|
39
|
+
message = entry["message"]
|
|
40
|
+
message.is_a?(Hash) && MessageAccess.role(message) == "user"
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def selectable_entry?(entry)
|
|
44
|
+
!entry["id"].to_s.empty? && ["message", "compaction", "branch_summary"].include?(entry["type"])
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
require_relative "../message_access"
|
|
2
|
+
require_relative "../message_text"
|
|
3
|
+
require_relative "../session_tree_tool_display"
|
|
4
|
+
require_relative "../tools/tool_call"
|
|
5
|
+
|
|
6
|
+
# Namespace for the Kward CLI agent runtime.
|
|
7
|
+
module Kward
|
|
8
|
+
# JSON-RPC backend namespace used by UI clients.
|
|
9
|
+
module RPC
|
|
10
|
+
# Builds frontend-neutral RPC row payloads from a persisted session tree.
|
|
11
|
+
#
|
|
12
|
+
# `SessionManager` owns session lifecycle and decides when a tree is needed;
|
|
13
|
+
# this class owns only the mechanics of flattening tree nodes into the row
|
|
14
|
+
# fields sent over JSON-RPC. Keeping row presentation here prevents the RPC
|
|
15
|
+
# session coordinator from accumulating rendering details while preserving the
|
|
16
|
+
# exact Tauren-compatible payload shape.
|
|
17
|
+
class SessionTreeRows
|
|
18
|
+
# @param roots [Array<Hash>] tree roots returned by `SessionStore#session_tree`
|
|
19
|
+
# @param current_leaf [String, nil] active persisted tree leaf id
|
|
20
|
+
# @param selectable [#call] predicate receiving an entry hash
|
|
21
|
+
def initialize(roots:, current_leaf:, selectable:)
|
|
22
|
+
@roots = roots
|
|
23
|
+
@current_leaf = current_leaf
|
|
24
|
+
@selectable = selectable
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Returns flattened RPC row hashes in active-path-first display order.
|
|
28
|
+
#
|
|
29
|
+
# @return [Array<Hash>] rows for the `session/tree` RPC method
|
|
30
|
+
def rows
|
|
31
|
+
active_path = tree_active_path(@roots, @current_leaf)
|
|
32
|
+
tool_calls_by_id = tree_tool_calls(@roots)
|
|
33
|
+
visible_roots = @roots.flat_map { |root| visible_tree_nodes(root) }
|
|
34
|
+
multiple_roots = visible_roots.length > 1
|
|
35
|
+
result = []
|
|
36
|
+
|
|
37
|
+
walk = lambda do |node, indent, just_branched, show_connector, is_last, gutters, virtual_root_child|
|
|
38
|
+
entry = node[:source]["entry"] || {}
|
|
39
|
+
entry_id = entry["id"].to_s
|
|
40
|
+
formatted = tree_entry_display(entry, tool_calls_by_id)
|
|
41
|
+
display_indent = multiple_roots ? [indent - 1, 0].max : indent
|
|
42
|
+
result << {
|
|
43
|
+
entryId: entry_id,
|
|
44
|
+
parentId: entry["parentId"],
|
|
45
|
+
role: formatted[:role],
|
|
46
|
+
text: formatted[:text],
|
|
47
|
+
current: !@current_leaf.to_s.empty? && entry_id == @current_leaf.to_s,
|
|
48
|
+
depth: display_indent,
|
|
49
|
+
isLast: is_last,
|
|
50
|
+
ancestorContinues: gutters.map { |gutter| gutter[:show] },
|
|
51
|
+
activePath: active_path.include?(entry_id),
|
|
52
|
+
selectable: @selectable.call(entry),
|
|
53
|
+
label: node[:source]["label"] || entry["resolvedLabel"],
|
|
54
|
+
labelTimestamp: node[:source]["labelTimestamp"],
|
|
55
|
+
prefix: tree_prefix(display_indent, gutters, show_connector && !virtual_root_child, is_last, !node[:children].empty?)
|
|
56
|
+
}.compact
|
|
57
|
+
|
|
58
|
+
children = node[:children].sort_by { |child| tree_contains_active_path?(child, active_path) ? 0 : 1 }
|
|
59
|
+
multiple_children = children.length > 1
|
|
60
|
+
child_indent = if multiple_children
|
|
61
|
+
indent + 1
|
|
62
|
+
elsif just_branched && indent.positive?
|
|
63
|
+
indent + 1
|
|
64
|
+
else
|
|
65
|
+
indent
|
|
66
|
+
end
|
|
67
|
+
connector_position = [display_indent - 1, 0].max
|
|
68
|
+
child_gutters = show_connector && !virtual_root_child ? gutters + [{ position: connector_position, show: !is_last }] : gutters
|
|
69
|
+
children.each_with_index do |child, index|
|
|
70
|
+
walk.call(child, child_indent, multiple_children, multiple_children, index == children.length - 1, child_gutters, false)
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
visible_roots.sort_by { |root| tree_contains_active_path?(root, active_path) ? 0 : 1 }.each_with_index do |root, index|
|
|
75
|
+
walk.call(root, multiple_roots ? 1 : 0, multiple_roots, multiple_roots, index == visible_roots.length - 1, [], multiple_roots)
|
|
76
|
+
end
|
|
77
|
+
result
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
private
|
|
81
|
+
|
|
82
|
+
def tree_active_path(roots, leaf_id)
|
|
83
|
+
by_id = tree_entries_by_id(roots)
|
|
84
|
+
ids = []
|
|
85
|
+
current = by_id[leaf_id.to_s]
|
|
86
|
+
while current
|
|
87
|
+
ids << current["id"].to_s
|
|
88
|
+
current = by_id[current["parentId"].to_s]
|
|
89
|
+
end
|
|
90
|
+
ids
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def tree_entries_by_id(roots)
|
|
94
|
+
roots.each_with_object({}) do |root, map|
|
|
95
|
+
stack = [root]
|
|
96
|
+
until stack.empty?
|
|
97
|
+
node = stack.pop
|
|
98
|
+
entry = node["entry"] || {}
|
|
99
|
+
map[entry["id"].to_s] = entry unless entry["id"].to_s.empty?
|
|
100
|
+
stack.concat(Array(node["children"]))
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def visible_tree_nodes(node)
|
|
106
|
+
children = Array(node["children"]).flat_map { |child| visible_tree_nodes(child) }
|
|
107
|
+
return children if hidden_tree_entry?(node["entry"] || {})
|
|
108
|
+
|
|
109
|
+
[{ source: node, children: children }]
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def hidden_tree_entry?(entry)
|
|
113
|
+
return false if @current_leaf && entry["id"].to_s == @current_leaf.to_s
|
|
114
|
+
return false unless entry["type"] == "message"
|
|
115
|
+
|
|
116
|
+
message = entry["message"]
|
|
117
|
+
return false unless message.is_a?(Hash) && MessageAccess.role(message) == "assistant"
|
|
118
|
+
|
|
119
|
+
content = MessageAccess.content(message)
|
|
120
|
+
content_tool_calls = content.is_a?(Array) && content.any? { |part| ToolCall.value(part, :type) == "toolCall" }
|
|
121
|
+
(content_tool_calls && !tree_text_content?(content)) || (!MessageAccess.tool_calls(message).empty? && MessageText.full_text(message).empty?)
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def tree_text_content?(content)
|
|
125
|
+
Array(content).any? { |part| ToolCall.value(part, :type) == "text" && ToolCall.value(part, :text).to_s.strip != "" }
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def tree_contains_active_path?(node, active_path)
|
|
129
|
+
entry_id = (node[:source]["entry"] || {})["id"].to_s
|
|
130
|
+
active_path.include?(entry_id) || node[:children].any? { |child| tree_contains_active_path?(child, active_path) }
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def tree_tool_calls(roots)
|
|
134
|
+
roots.each_with_object({}) do |root, tool_calls_by_id|
|
|
135
|
+
stack = [root]
|
|
136
|
+
until stack.empty?
|
|
137
|
+
node = stack.pop
|
|
138
|
+
entry = node["entry"] || {}
|
|
139
|
+
message = entry["message"]
|
|
140
|
+
if entry["type"] == "message" && message.is_a?(Hash) && MessageAccess.role(message) == "assistant"
|
|
141
|
+
MessageAccess.tool_calls(message).each { |tool_call| tool_calls_by_id[ToolCall.id(tool_call).to_s] = tool_call }
|
|
142
|
+
end
|
|
143
|
+
stack.concat(Array(node["children"]))
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def tree_entry_display(entry, tool_calls_by_id = {})
|
|
149
|
+
case entry["type"]
|
|
150
|
+
when "message"
|
|
151
|
+
message = entry["message"] || {}
|
|
152
|
+
role = MessageAccess.role(message).to_s
|
|
153
|
+
return { role: "tool", text: format_tool_result(message, tool_calls_by_id) } if ["tool", "toolResult"].include?(role)
|
|
154
|
+
return { role: role.empty? ? "message" : role, text: display_message_text(message) }
|
|
155
|
+
when "compaction"
|
|
156
|
+
return { role: "compaction", text: display_message_text(entry["message"] || {}) }
|
|
157
|
+
when "branch_summary"
|
|
158
|
+
return { role: "summary", text: truncate_tree_text(entry["summary"]) }
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
{ role: entry["type"].to_s.empty? ? "entry" : entry["type"].to_s, text: entry["type"].to_s }
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def tree_prefix(display_indent, gutters, show_connector, is_last, foldable)
|
|
165
|
+
return "" if display_indent.to_i <= 0
|
|
166
|
+
|
|
167
|
+
connector_position = show_connector ? display_indent - 1 : -1
|
|
168
|
+
(0...(display_indent * 3)).map do |index|
|
|
169
|
+
level = index / 3
|
|
170
|
+
position = index % 3
|
|
171
|
+
gutter = gutters.find { |candidate| candidate[:position] == level }
|
|
172
|
+
|
|
173
|
+
if gutter
|
|
174
|
+
position.zero? && gutter[:show] ? "│" : " "
|
|
175
|
+
elsif show_connector && level == connector_position
|
|
176
|
+
if position.zero?
|
|
177
|
+
is_last ? "└" : "├"
|
|
178
|
+
elsif position == 1
|
|
179
|
+
foldable ? "⊟" : "─"
|
|
180
|
+
else
|
|
181
|
+
" "
|
|
182
|
+
end
|
|
183
|
+
else
|
|
184
|
+
" "
|
|
185
|
+
end
|
|
186
|
+
end.join
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
def format_tool_result(message, tool_calls_by_id)
|
|
190
|
+
tool_call = tool_calls_by_id[MessageAccess.tool_call_id(message).to_s]
|
|
191
|
+
return SessionTreeToolDisplay.label(tool_call) if tool_call
|
|
192
|
+
|
|
193
|
+
name = MessageAccess.tool_name(message).to_s
|
|
194
|
+
name = "tool" if name.empty?
|
|
195
|
+
"[#{name}]"
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
def display_message_text(message)
|
|
199
|
+
truncate_tree_text(MessageText.full_text(message))
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
def truncate_tree_text(text)
|
|
203
|
+
normalized = text.to_s.gsub(/\s+/, " ").strip
|
|
204
|
+
normalized.length > 120 ? "#{normalized.slice(0, 117)}..." : normalized
|
|
205
|
+
end
|
|
206
|
+
end
|
|
207
|
+
end
|
|
208
|
+
end
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
require "time"
|
|
2
2
|
require_relative "tool_metadata"
|
|
3
3
|
|
|
4
|
+
# Namespace for the Kward CLI agent runtime.
|
|
4
5
|
module Kward
|
|
6
|
+
# JSON-RPC backend namespace used by UI clients.
|
|
5
7
|
module RPC
|
|
8
|
+
# Converts tool calls and results into RPC event payloads.
|
|
6
9
|
class ToolEventNormalizer
|
|
7
10
|
def initialize(tool_call, content: nil)
|
|
8
11
|
@tool_call = tool_call
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
require_relative "../tools/tool_call"
|
|
2
2
|
require_relative "../workspace"
|
|
3
3
|
|
|
4
|
+
# Namespace for the Kward CLI agent runtime.
|
|
4
5
|
module Kward
|
|
6
|
+
# JSON-RPC backend namespace used by UI clients.
|
|
5
7
|
module RPC
|
|
8
|
+
# Builds compact metadata for RPC tool-call display.
|
|
6
9
|
module ToolMetadata
|
|
7
10
|
module_function
|
|
8
11
|
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
require_relative "../tools/tool_call"
|
|
2
2
|
require_relative "tool_metadata"
|
|
3
3
|
|
|
4
|
+
# Namespace for the Kward CLI agent runtime.
|
|
4
5
|
module Kward
|
|
6
|
+
# JSON-RPC backend namespace used by UI clients.
|
|
5
7
|
module RPC
|
|
8
|
+
# Normalizes Kward transcript messages into Tauren-compatible RPC payloads.
|
|
6
9
|
class TranscriptNormalizer
|
|
7
10
|
IMAGE_MIME_TYPES = ["image/png", "image/jpeg", "image/gif", "image/webp"].freeze
|
|
8
11
|
THINKING_CONTENT_TYPES = ["thinking", "reasoning"].freeze
|
data/lib/kward/rpc/transport.rb
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
require "json"
|
|
2
2
|
|
|
3
|
+
# Namespace for the Kward CLI agent runtime.
|
|
3
4
|
module Kward
|
|
5
|
+
# JSON-RPC backend namespace used by UI clients.
|
|
4
6
|
module RPC
|
|
7
|
+
# Line-delimited JSON-RPC transport over input and output streams.
|
|
5
8
|
class Transport
|
|
6
9
|
def initialize(input:, output:)
|
|
7
10
|
@input = input
|