samagotchi 0.66.0 → 0.67.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 +60 -1
- data/docs/architecture.md +50 -28
- data/docs/cli.md +3 -1
- data/docs/configuration.md +50 -9
- data/docs/context.md +35 -1
- data/docs/desktop.md +90 -22
- data/docs/guardrails.md +8 -5
- data/docs/hooks.md +2 -2
- data/docs/internals/anthropic.md +156 -0
- data/docs/internals/background-tasks.md +1 -1
- data/docs/internals/bundles.md +2 -0
- data/docs/internals/engine.md +71 -29
- data/docs/internals/gemma4-contract.md +1 -1
- data/docs/internals/llm-context-bench.md +5 -4
- data/docs/internals/prompt-caching.md +26 -11
- data/docs/plugins.md +126 -23
- data/docs/sessions.md +6 -6
- data/docs/sub-agent.md +4 -1
- data/docs/testing.md +17 -3
- data/lib/samagotchi/bootstrap/probe.rb +4 -3
- data/lib/samagotchi/bootstrap_command.rb +13 -3
- data/lib/samagotchi/bridge/ring_buffer.rb +0 -5
- data/lib/samagotchi/bridge.rb +6 -3
- data/lib/samagotchi/bundles/claude-peer/manifest.yml +7 -6
- data/lib/samagotchi/bundles/claude-peer/plugin.rb +34 -313
- data/lib/samagotchi/bundles/claude-peer/scripts/chi-peer-SKILL.md +8 -3
- data/lib/samagotchi/bundles/claude-peer/skill_claude_peer.md +14 -6
- data/lib/samagotchi/bundles/codex-peer/manifest.yml +7 -6
- data/lib/samagotchi/bundles/codex-peer/plugin.rb +37 -312
- data/lib/samagotchi/bundles/codex-peer/scripts/chi-peer-SKILL.md +8 -3
- data/lib/samagotchi/bundles/codex-peer/skill_codex_peer.md +14 -6
- data/lib/samagotchi/bundles/mcp/manifest.yml +5 -5
- data/lib/samagotchi/bundles/mcp/plugin.rb +82 -294
- data/lib/samagotchi/bundles/system/config_modification_protocol.md +7 -3
- data/lib/samagotchi/bundles/system/manifest.yml +3 -3
- data/lib/samagotchi/bundles/system/self_map.md +1 -1
- data/lib/samagotchi/config.rb +15 -7
- data/lib/samagotchi/context_absorber.rb +66 -13
- data/lib/samagotchi/desktop/macos/AgentTerminal.swift +260 -0
- data/lib/samagotchi/desktop/macos/App.swift +3 -3
- data/lib/samagotchi/desktop/macos/ChiRunner.swift +5 -2
- data/lib/samagotchi/desktop/macos/Images.swift +2 -2
- data/lib/samagotchi/desktop/macos/Kitty.swift +33 -197
- data/lib/samagotchi/desktop/macos/Panel.swift +118 -82
- data/lib/samagotchi/desktop/macos/ProcessRunner.swift +1 -1
- data/lib/samagotchi/desktop/macos/Tmux.swift +239 -0
- data/lib/samagotchi/desktop/macos.rb +84 -13
- data/lib/samagotchi/desktop_command.rb +14 -3
- data/lib/samagotchi/engine.rb +96 -191
- data/lib/samagotchi/engine_peers.rb +3 -0
- data/lib/samagotchi/guardrail_wiring.rb +1 -1
- data/lib/samagotchi/guardrails/approval.rb +1 -1
- data/lib/samagotchi/guardrails/approvals.rb +1 -1
- data/lib/samagotchi/guardrails/rules.rb +1 -1
- data/lib/samagotchi/guardrails/targets.rb +1 -1
- data/lib/samagotchi/hook_routes.rb +151 -0
- data/lib/samagotchi/hooks/registry.rb +2 -2
- data/lib/samagotchi/host_keys.rb +147 -0
- data/lib/samagotchi/llm/adapter_common.rb +138 -0
- data/lib/samagotchi/llm/anthropic_messages.rb +28 -105
- data/lib/samagotchi/llm/api_key.rb +23 -7
- data/lib/samagotchi/llm/chat_loop.rb +10 -6
- data/lib/samagotchi/llm/openai_chat.rb +9 -92
- data/lib/samagotchi/llm/refusal_memo.rb +29 -0
- data/lib/samagotchi/llm_context_override.rb +1 -0
- data/lib/samagotchi/peer_sources.rb +58 -0
- data/lib/samagotchi/plugin/api.rb +64 -155
- data/lib/samagotchi/plugin/loader.rb +4 -2
- data/lib/samagotchi/plugin/peer.rb +394 -0
- data/lib/samagotchi/plugin/tool_spec.rb +166 -0
- data/lib/samagotchi/plugin_tasks.rb +2 -2
- data/lib/samagotchi/row_refs.rb +1 -1
- data/lib/samagotchi/self_report.rb +5 -3
- data/lib/samagotchi/send_command.rb +21 -11
- data/lib/samagotchi/session_continue.rb +58 -15
- data/lib/samagotchi/session_inbox.rb +0 -13
- data/lib/samagotchi/tool_call_parser.rb +1 -1
- data/lib/samagotchi/toolkit_binding.rb +138 -0
- data/lib/samagotchi/tools/context_read.rb +14 -0
- data/lib/samagotchi/tools/execute.rb +12 -12
- data/lib/samagotchi/tools/registry.rb +1 -1
- data/lib/samagotchi/tools/task_create.rb +6 -6
- data/lib/samagotchi/tools/task_get.rb +13 -13
- data/lib/samagotchi/tools/task_list.rb +6 -6
- data/lib/samagotchi/tools/task_record.rb +52 -0
- data/lib/samagotchi/tools/task_runtime.rb +55 -91
- data/lib/samagotchi/tools/task_stop.rb +6 -6
- data/lib/samagotchi/tools/task_wait.rb +12 -13
- data/lib/samagotchi/tools/tool_index.rb +454 -0
- data/lib/samagotchi/update_command.rb +9 -6
- data/lib/samagotchi/version.rb +1 -1
- data/lib/samagotchi/web/app.rb +16 -9
- data/lib/samagotchi/web/capabilities.rb +1 -1
- data/lib/samagotchi/web/public/activity.js +2 -2
- data/lib/samagotchi/web/public/annotate_actions.js +1 -1
- data/lib/samagotchi/web/public/app.js +31 -36
- data/lib/samagotchi/web/public/chains.js +1 -1
- data/lib/samagotchi/web/public/commands.js +2 -2
- data/lib/samagotchi/web/public/composer_size.js +2 -2
- data/lib/samagotchi/web/public/copy.js +1 -1
- data/lib/samagotchi/web/public/ctx.js +1 -1
- data/lib/samagotchi/web/public/delivery_line.js +4 -4
- data/lib/samagotchi/web/public/families_view.js +1 -1
- data/lib/samagotchi/web/public/format.js +11 -7
- data/lib/samagotchi/web/public/images.js +25 -2
- data/lib/samagotchi/web/public/llm_context_chip.js +2 -2
- data/lib/samagotchi/web/public/notify.js +6 -4
- data/lib/samagotchi/web/public/question_card.js +1 -1
- data/lib/samagotchi/web/public/ref_scan.js +1 -1
- data/lib/samagotchi/web/public/session_state.js +14 -0
- data/lib/samagotchi/web/public/session_title.js +2 -3
- data/lib/samagotchi/web/public/sessions_list.js +0 -8
- data/lib/samagotchi/web/public/stage_model.js +2 -2
- data/lib/samagotchi/web/public/strip.js +3 -3
- data/lib/samagotchi/web/public/task_stop.js +3 -2
- data/lib/samagotchi/web/public/turn_events.js +1 -1
- data/lib/samagotchi/web/public/turn_html.js +2 -2
- data/lib/samagotchi/web/public/turn_model.js +2 -2
- data/lib/samagotchi/web/public/turn_view.js +1 -1
- data/lib/samagotchi/web/server.rb +0 -4
- data/lib/samagotchi/worker.rb +11 -8
- data/lib/samagotchi/worker_inbound.rb +36 -4
- data/lib/samagotchi/worker_wakes.rb +67 -7
- metadata +14 -1
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "json"
|
|
4
3
|
require_relative "../toolkit"
|
|
5
4
|
require_relative "../log"
|
|
6
5
|
require_relative "../hooks/registry"
|
|
7
|
-
require_relative "../tools/args"
|
|
8
6
|
require_relative "../commands/registry"
|
|
9
7
|
require_relative "service"
|
|
10
8
|
require_relative "tool_result"
|
|
9
|
+
require_relative "tool_spec"
|
|
11
10
|
|
|
12
11
|
module Samagotchi
|
|
13
12
|
module Plugin
|
|
@@ -16,8 +15,6 @@ module Samagotchi
|
|
|
16
15
|
# #register returns, so a plugin that raises halfway adds nothing.
|
|
17
16
|
class Api
|
|
18
17
|
COMMAND_NAME = %r{\A/[a-z][a-z0-9_-]{0,31}\z}
|
|
19
|
-
TOOL_NAME = /\A[a-z][a-z0-9_]{0,47}\z/
|
|
20
|
-
PARAM_NAME = /\A[a-z_][a-z0-9_]*\z/i
|
|
21
18
|
ANNOTATION_LABEL_MAX = 40
|
|
22
19
|
ANNOTATIONS_MAX = 5
|
|
23
20
|
|
|
@@ -38,6 +35,8 @@ module Samagotchi
|
|
|
38
35
|
@services = []
|
|
39
36
|
@inits = []
|
|
40
37
|
@toolkits = []
|
|
38
|
+
@peer_sources = []
|
|
39
|
+
@tool_source = nil
|
|
41
40
|
@committed = false
|
|
42
41
|
end
|
|
43
42
|
|
|
@@ -118,11 +117,11 @@ module Samagotchi
|
|
|
118
117
|
# a list of {kind:, target:, label:, actions:} (RowRefs, Ref). Run
|
|
119
118
|
# once per call on the tool path, so it must be cheap (no I/O)
|
|
120
119
|
def tool(name, description, **, &)
|
|
121
|
-
spec =
|
|
122
|
-
if (taken = @registries.tools[spec
|
|
123
|
-
raise ArgumentError, "tool #{spec
|
|
120
|
+
spec = ToolSpec.build(name, description, **, &)
|
|
121
|
+
if (taken = @registries.tools[spec.name])
|
|
122
|
+
raise ArgumentError, "tool #{spec.name} is already registered (#{taken.source})"
|
|
124
123
|
end
|
|
125
|
-
raise ArgumentError, "tool #{spec
|
|
124
|
+
raise ArgumentError, "tool #{spec.name} is registered twice" if @tools.any? { |tool| tool.name == spec.name }
|
|
126
125
|
|
|
127
126
|
@tools << spec
|
|
128
127
|
nil
|
|
@@ -146,7 +145,7 @@ module Samagotchi
|
|
|
146
145
|
yield set
|
|
147
146
|
if (stage = @registries.stage_tools)
|
|
148
147
|
stage.call(@bundle, set.specs, @context)
|
|
149
|
-
elsif
|
|
148
|
+
elsif ToolSpec.apply(@registries.tools, @bundle, set.specs, @context)[:changed]
|
|
150
149
|
tools_changed!
|
|
151
150
|
end
|
|
152
151
|
nil
|
|
@@ -154,7 +153,7 @@ module Samagotchi
|
|
|
154
153
|
|
|
155
154
|
# What #replace_tools' block declares tools on.
|
|
156
155
|
class ToolSet
|
|
157
|
-
# @return [Array<
|
|
156
|
+
# @return [Array<ToolSpec>]
|
|
158
157
|
attr_reader :specs
|
|
159
158
|
|
|
160
159
|
def initialize
|
|
@@ -163,8 +162,8 @@ module Samagotchi
|
|
|
163
162
|
|
|
164
163
|
# As Api#tool.
|
|
165
164
|
def tool(name, description, **, &)
|
|
166
|
-
spec =
|
|
167
|
-
raise ArgumentError, "tool #{spec
|
|
165
|
+
spec = ToolSpec.build(name, description, **, &)
|
|
166
|
+
raise ArgumentError, "tool #{spec.name} is registered twice" if @specs.any? { |s| s.name == spec.name }
|
|
168
167
|
|
|
169
168
|
@specs << spec
|
|
170
169
|
nil
|
|
@@ -250,6 +249,32 @@ module Samagotchi
|
|
|
250
249
|
nil
|
|
251
250
|
end
|
|
252
251
|
|
|
252
|
+
# Publish tools into the session's tool index instead of declaring
|
|
253
|
+
# them one by one (docs/plugins.md, "Publishing tools to the index"):
|
|
254
|
+
# the model searches them with find_tools and calls one with
|
|
255
|
+
# call_tool (<group>/<tool>), which chi declares once, after the
|
|
256
|
+
# plugins load, when a source registered. Returns the source: its
|
|
257
|
+
# #publish(groups) (ToolIndex::Group values, each with its
|
|
258
|
+
# ToolIndex::Tool values) swaps in the plugin's whole list, from any
|
|
259
|
+
# thread, at any time, and changes only what searches answer.
|
|
260
|
+
# @param label [String] the call tool's row label (default: the bundle)
|
|
261
|
+
# @param call [#call] (tool, args, ctx) → the call's result, as a
|
|
262
|
+
# tool's block returns it; +tool+ a ToolIndex::Tool the index looked
|
|
263
|
+
# up and checked, +args+ typed by its schema
|
|
264
|
+
# @param acts_as [#call] (group, tool) → the name guardrail rules
|
|
265
|
+
# match for a tool from its names alone (one not published yet)
|
|
266
|
+
# @param wait [#call, nil] (group) waits for a :starting group's start
|
|
267
|
+
# @param prefix [String, nil] a bare name with it is taken as an
|
|
268
|
+
# acts_as as given
|
|
269
|
+
# @return [Tools::ToolIndex::Source]
|
|
270
|
+
def tool_source(call:, acts_as:, label: @bundle, wait: nil, prefix: nil)
|
|
271
|
+
raise ArgumentError, "this chi has no tool index (plugins: false)" unless @registries.tool_index
|
|
272
|
+
raise ArgumentError, "tool_source is declared twice" if @tool_source
|
|
273
|
+
|
|
274
|
+
@tool_source = @registries.tool_index.source(label: label, call: call, acts_as: acts_as, wait: wait,
|
|
275
|
+
prefix: prefix, context: @context)
|
|
276
|
+
end
|
|
277
|
+
|
|
253
278
|
TOOLKIT_NAME = /\A[a-z][a-z0-9_-]{0,31}\z/
|
|
254
279
|
|
|
255
280
|
# A named toolkit sessions can be put on (/toolkit NAME, chi --toolkit,
|
|
@@ -273,11 +298,29 @@ module Samagotchi
|
|
|
273
298
|
nil
|
|
274
299
|
end
|
|
275
300
|
|
|
301
|
+
# A context source that holds a peer agent's answers (the peer
|
|
302
|
+
# bundles' claude-review, codex-review): +label+ is the peer's name
|
|
303
|
+
# ("Codex", PeerSources::LABEL), said in chi's own lines. Such a
|
|
304
|
+
# source, when it is a pushed one of the session's own, wakes on
|
|
305
|
+
# context.peer_turns rather than the 10-minute window, with the peer
|
|
306
|
+
# line (docs/context.md, Waking). Declared here, in-process, never by
|
|
307
|
+
# a source file; gone with the bundle.
|
|
308
|
+
def peer_source(name, label:)
|
|
309
|
+
raise ArgumentError, "this chi has no peer sources (plugins: false)" unless @registries.peer_sources
|
|
310
|
+
|
|
311
|
+
name = @registries.peer_sources.check!(name, label: label, bundle: @bundle)
|
|
312
|
+
raise ArgumentError, "peer source #{name} is declared twice" if @peer_sources.any? { |item| item[:name] == name }
|
|
313
|
+
|
|
314
|
+
@peer_sources << { name: name, label: label.to_s }
|
|
315
|
+
nil
|
|
316
|
+
end
|
|
317
|
+
|
|
276
318
|
# Register what was staged. Called by Loader after #register. What
|
|
277
319
|
# can only be checked now (an annotation's command) is checked before
|
|
278
320
|
# anything is registered: a raise here leaves the registries as they were.
|
|
279
321
|
def commit!
|
|
280
322
|
check_annotations!
|
|
323
|
+
@peer_sources.each { |item| @registries.peer_sources.check!(item[:name], label: item[:label], bundle: @bundle) }
|
|
281
324
|
context = @context
|
|
282
325
|
@commands.each do |cmd|
|
|
283
326
|
block = cmd[:block]
|
|
@@ -287,7 +330,7 @@ module Samagotchi
|
|
|
287
330
|
block.call(args, context)
|
|
288
331
|
end
|
|
289
332
|
end
|
|
290
|
-
@tools.each { |tool| @registries.tools.register(tool
|
|
333
|
+
@tools.each { |tool| @registries.tools.register(tool.name, source: @bundle, **tool.entry_fields(context)) }
|
|
291
334
|
@hooks.each do |hook|
|
|
292
335
|
block = hook[:block]
|
|
293
336
|
# Logged only (echo: false): a turn's live region is on screen.
|
|
@@ -307,12 +350,15 @@ module Samagotchi
|
|
|
307
350
|
end
|
|
308
351
|
end
|
|
309
352
|
@registries.toolkits&.concat(@toolkits)
|
|
353
|
+
@registries.tool_index.add(@tool_source) if @tool_source
|
|
354
|
+
@peer_sources.each { |item| @registries.peer_sources.declare(item[:name], label: item[:label], bundle: @bundle) }
|
|
310
355
|
@committed = true
|
|
311
356
|
end
|
|
312
357
|
|
|
313
358
|
# @return [Hash] how many of each it registered (for the log)
|
|
314
359
|
def counts = { commands: @commands.size, annotations: @annotations.size, tools: @tools.size, hooks: @hooks.size,
|
|
315
|
-
services: @services.size, inits: @inits.size, toolkits: @toolkits.size
|
|
360
|
+
services: @services.size, inits: @inits.size, toolkits: @toolkits.size,
|
|
361
|
+
tool_sources: @tool_source ? 1 : 0, peer_sources: @peer_sources.size }
|
|
316
362
|
|
|
317
363
|
# Each annotation's command is one #command staged.
|
|
318
364
|
def check_annotations!
|
|
@@ -325,147 +371,10 @@ module Samagotchi
|
|
|
325
371
|
end
|
|
326
372
|
private :check_annotations!
|
|
327
373
|
|
|
328
|
-
# A checked tool declaration (
|
|
329
|
-
#
|
|
330
|
-
# @return [
|
|
331
|
-
def self.tool_spec(
|
|
332
|
-
&block)
|
|
333
|
-
raise ArgumentError, "tool #{name.inspect} needs a block" unless block
|
|
334
|
-
|
|
335
|
-
name = name.to_s
|
|
336
|
-
raise ArgumentError, "tool name #{name.inspect} must be a-z, 0-9 and _ (at most 48)" unless name.match?(TOOL_NAME)
|
|
337
|
-
raise ArgumentError, "tool #{name}: preview must respond to #call" if preview && !preview.respond_to?(:call)
|
|
338
|
-
raise ArgumentError, "tool #{name}: targets must respond to #call" if targets && !targets.respond_to?(:call)
|
|
339
|
-
raise ArgumentError, "tool #{name}: refs must respond to #call" if refs && !refs.respond_to?(:call)
|
|
340
|
-
|
|
341
|
-
parameters = schema ? schema_parameters(name, schema) : params_schema(name, params)
|
|
342
|
-
{ name: name, schema: { name: name, description: description.to_s, parameters: parameters },
|
|
343
|
-
label: label&.to_s, preview: preview, targets: targets, refs: refs, block: block }
|
|
344
|
-
end
|
|
345
|
-
|
|
346
|
-
# Make +bundle+'s tools in +registry+ the +specs+: its tools not in
|
|
347
|
-
# them go; a new one, or one whose schema or label changed, is
|
|
348
|
-
# registered (a changed one again, in its place: Tools::Registry
|
|
349
|
-
# orders bundle tools by bundle and name); an unchanged one is kept
|
|
350
|
-
# as it is, but for its block, preview:, targets: and refs:, swapped
|
|
351
|
-
# in place: none of them reaches the prompt, so not a change (that
|
|
352
|
-
# would build the prompts again). A name
|
|
353
|
-
# another source has is left out. Called on the turn thread
|
|
354
|
-
# (Engine#apply_staged_tools!).
|
|
355
|
-
# @return [Hash] {changed: Boolean, skipped: [String] (why)}
|
|
356
|
-
def self.apply_tools(registry, bundle, specs, context)
|
|
357
|
-
wanted = specs.to_h { |spec| [spec[:name], spec] }
|
|
358
|
-
changed = false
|
|
359
|
-
registry.by_source(bundle).each do |entry|
|
|
360
|
-
if (spec = wanted[entry.name]) && spec[:schema] == entry.schema && spec[:label] == entry.label
|
|
361
|
-
entry_fields(spec, context).slice(:handler, :preview, :targets, :refs).each { |key, value| entry[key] = value }
|
|
362
|
-
next
|
|
363
|
-
end
|
|
364
|
-
|
|
365
|
-
registry.unregister(entry.name)
|
|
366
|
-
changed = true
|
|
367
|
-
end
|
|
368
|
-
skipped = []
|
|
369
|
-
specs.each do |spec|
|
|
370
|
-
if (taken = registry[spec[:name]])
|
|
371
|
-
skipped << "tool #{spec[:name]} is already registered (#{taken.source})" unless taken.source == bundle
|
|
372
|
-
next
|
|
373
|
-
end
|
|
374
|
-
|
|
375
|
-
registry.register(spec[:name], source: bundle, **entry_fields(spec, context))
|
|
376
|
-
changed = true
|
|
377
|
-
end
|
|
378
|
-
{ changed: changed, skipped: skipped }
|
|
379
|
-
end
|
|
380
|
-
|
|
381
|
-
# Registry#register's keywords for a tool declaration: its block
|
|
382
|
-
# wrapped as a handler (typed args, the Context), preview, targets, refs.
|
|
383
|
-
def self.entry_fields(spec, context)
|
|
384
|
-
block = spec[:block]
|
|
385
|
-
parameters = spec[:schema][:parameters]
|
|
386
|
-
preview = per_call(spec[:preview], parameters)
|
|
387
|
-
{
|
|
388
|
-
schema: spec[:schema], label: spec[:label],
|
|
389
|
-
handler: lambda { |call, _kctx|
|
|
390
|
-
result = block.call(Api.args_of(call, parameters), context)
|
|
391
|
-
Api.result_text(result)
|
|
392
|
-
},
|
|
393
|
-
preview: preview && ->(call) { preview.call(call)&.to_s },
|
|
394
|
-
targets: per_call(spec[:targets], parameters),
|
|
395
|
-
refs: per_call(spec[:refs], parameters)
|
|
396
|
-
}
|
|
397
|
-
end
|
|
398
|
-
|
|
399
|
-
# A plugin's args → value lambda (+fn+) as call → value: it gets the
|
|
400
|
-
# call's typed args (Api.args_of), as the tool's block does. nil
|
|
401
|
-
# without one.
|
|
402
|
-
def self.per_call(fn, parameters)
|
|
403
|
-
fn && ->(call) { fn.call(Api.args_of(call, parameters)) }
|
|
404
|
-
end
|
|
405
|
-
|
|
406
|
-
# A tool's result as the model reads it: a String (a ToolResult too,
|
|
407
|
-
# with its images) as it is, a Hash or Array as JSON (its #to_s is
|
|
408
|
-
# Hash#inspect, which changes with the Ruby version), else #to_s.
|
|
409
|
-
def self.result_text(result)
|
|
410
|
-
case result
|
|
411
|
-
when String then result
|
|
412
|
-
when Hash, Array then JSON.generate(result)
|
|
413
|
-
else result.to_s
|
|
414
|
-
end
|
|
415
|
-
rescue JSON::GeneratorError
|
|
416
|
-
result.to_s
|
|
417
|
-
end
|
|
418
|
-
|
|
419
|
-
# A tool call's arguments as a plugin sees them: the parsers' args:
|
|
420
|
-
# (a call built without one, in a spec say: the call without its
|
|
421
|
-
# name), string keys, typed by +parameters+, frozen.
|
|
422
|
-
def self.args_of(call, parameters = nil)
|
|
423
|
-
given = call[:args].is_a?(Hash) ? call[:args] : call.reject { |key, _| %i[name args].include?(key) }
|
|
424
|
-
Tools::Args.coerce(given, parameters).freeze
|
|
425
|
-
end
|
|
426
|
-
|
|
427
|
-
# A JSON Schema with symbol keys (property names included), as
|
|
428
|
-
# ToolDeclarations::TOOL_SCHEMAS has them.
|
|
429
|
-
def self.symbolize(value)
|
|
430
|
-
case value
|
|
431
|
-
when Hash then value.to_h { |key, item| [key.to_sym, symbolize(item)] }
|
|
432
|
-
when Array then value.map { |item| symbolize(item) }
|
|
433
|
-
else value
|
|
434
|
-
end
|
|
435
|
-
end
|
|
436
|
-
|
|
437
|
-
# The parameters object from params: (name => property spec).
|
|
438
|
-
def self.params_schema(name, params)
|
|
439
|
-
raise ArgumentError, "tool #{name}: params must be a Hash" unless params.is_a?(Hash)
|
|
440
|
-
|
|
441
|
-
required = []
|
|
442
|
-
properties = params.each_with_object({}) do |(param, spec), acc|
|
|
443
|
-
param = param.to_s
|
|
444
|
-
raise ArgumentError, "tool #{name}: parameter name #{param.inspect} is not a plain word" unless param.match?(PARAM_NAME)
|
|
445
|
-
raise ArgumentError, "tool #{name}: parameter #{param} must be a Hash {type:, description:}" unless spec.is_a?(Hash)
|
|
446
|
-
|
|
447
|
-
spec = Api.symbolize(spec)
|
|
448
|
-
required << param if spec.delete(:required) == true
|
|
449
|
-
acc[param.to_sym] = { type: (spec.delete(:type) || "string").to_s, description: spec.delete(:description).to_s }.merge(spec)
|
|
450
|
-
end
|
|
451
|
-
{ type: "object", properties: properties, required: required }
|
|
452
|
-
end
|
|
453
|
-
private_class_method :params_schema
|
|
454
|
-
|
|
455
|
-
# The parameters object from schema: (a JSON Schema object).
|
|
456
|
-
def self.schema_parameters(name, schema)
|
|
457
|
-
raise ArgumentError, "tool #{name}: schema must be a Hash" unless schema.is_a?(Hash)
|
|
458
|
-
|
|
459
|
-
schema = Api.symbolize(schema)
|
|
460
|
-
raise ArgumentError, "tool #{name}: schema must be {type: \"object\", properties: {…}}" unless schema.fetch(:type, "object").to_s == "object"
|
|
461
|
-
|
|
462
|
-
properties = schema[:properties] || {}
|
|
463
|
-
raise ArgumentError, "tool #{name}: schema properties must be a Hash" unless properties.is_a?(Hash)
|
|
464
|
-
|
|
465
|
-
{ type: "object", properties: properties, required: Array(schema[:required]).map(&:to_s) }
|
|
466
|
-
.merge(schema.except(:type, :properties, :required))
|
|
467
|
-
end
|
|
468
|
-
private_class_method :schema_parameters
|
|
374
|
+
# A checked tool declaration (ToolSpec.build), for bundles: the mcp
|
|
375
|
+
# bundle's token estimate calls it, installed ones included.
|
|
376
|
+
# @return [ToolSpec]
|
|
377
|
+
def self.tool_spec(...) = ToolSpec.build(...)
|
|
469
378
|
end
|
|
470
379
|
end
|
|
471
380
|
end
|
|
@@ -17,9 +17,11 @@ module Samagotchi
|
|
|
17
17
|
# Engine#shutdown stops; +stage_tools+ is (bundle, specs, context),
|
|
18
18
|
# chi.replace_tools' set, which the Engine applies on its turn thread;
|
|
19
19
|
# +init+ adds a chi.init task (Engine#add_init_task); +toolkits+ is the
|
|
20
|
-
# Engine's list of the plugins' toolkits (chi.toolkit: Toolkit::Kit)
|
|
20
|
+
# Engine's list of the plugins' toolkits (chi.toolkit: Toolkit::Kit);
|
|
21
|
+
# +tool_index+ the Engine's Tools::ToolIndex (chi.tool_source);
|
|
22
|
+
# +peer_sources+ the Engine's PeerSources (chi.peer_source).
|
|
21
23
|
Registries = Struct.new(:commands, :tools, :hooks, :context_for, :tools_changed, :services, :stage_tools, :init,
|
|
22
|
-
:toolkits, keyword_init: true)
|
|
24
|
+
:toolkits, :tool_index, :peer_sources, keyword_init: true)
|
|
23
25
|
|
|
24
26
|
# Loads installed bundles' plugins (manifest plugin: {file:, sha256:})
|
|
25
27
|
# into an Engine's registries (docs/plugins.md).
|