little_ghost 0.7.0 → 0.8.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.
Files changed (46) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +33 -5
  3. data/docs/guides/code_mode.md +2 -2
  4. data/docs/guides/getting_started.md +26 -1
  5. data/docs/guides/integrations.md +20 -161
  6. data/docs/guides/mcp.md +288 -0
  7. data/docs/guides/models_and_providers.md +21 -54
  8. data/docs/guides/production.md +14 -5
  9. data/docs/guides/providers.md +275 -0
  10. data/docs/guides/sandboxing.md +42 -7
  11. data/docs/guides/skills.md +1 -3
  12. data/docs/guides/tools.md +2 -0
  13. data/exe/little_ghost +12 -0
  14. data/lib/little_ghost/assembly.rb +11 -3
  15. data/lib/little_ghost/assembly_builder.rb +2 -2
  16. data/lib/little_ghost/cli.rb +138 -0
  17. data/lib/little_ghost/console.rb +17 -0
  18. data/lib/little_ghost/errors.rb +1 -1
  19. data/lib/little_ghost/generators/application_generator.rb +179 -0
  20. data/lib/little_ghost/generators/templates/application/Gemfile.tt +3 -0
  21. data/lib/little_ghost/generators/templates/application/README.md.tt +29 -0
  22. data/lib/little_ghost/generators/templates/application/app/agents/application_agent.rb.tt +5 -0
  23. data/lib/little_ghost/generators/templates/application/app/prompts/application/system.erb.tt +1 -0
  24. data/lib/little_ghost/generators/templates/application/bin/application.tt +10 -0
  25. data/lib/little_ghost/generators/templates/application/config/little_ghost.rb.tt +5 -0
  26. data/lib/little_ghost/generators/templates/application/gitignore.tt +2 -0
  27. data/lib/little_ghost/generators/templates/application/keep.tt +0 -0
  28. data/lib/little_ghost/mcp/client.rb +295 -606
  29. data/lib/little_ghost/mcp/toolset.rb +133 -95
  30. data/lib/little_ghost/mcp.rb +21 -3
  31. data/lib/little_ghost/model_resolver.rb +2 -0
  32. data/lib/little_ghost/models/catalog/models_dev_source.rb +4 -1
  33. data/lib/little_ghost/provider_registry.rb +1 -0
  34. data/lib/little_ghost/providers/lm_studio/catalog_source.rb +201 -0
  35. data/lib/little_ghost/providers/lm_studio.rb +52 -0
  36. data/lib/little_ghost/providers/openai.rb +3 -75
  37. data/lib/little_ghost/providers/openai_compatible.rb +78 -1
  38. data/lib/little_ghost/sandbox/environment_policy.rb +29 -4
  39. data/lib/little_ghost/sandbox/policy.rb +28 -6
  40. data/lib/little_ghost/sandboxes/unrestricted.rb +7 -3
  41. data/lib/little_ghost/skills/catalog.rb +13 -3
  42. data/lib/little_ghost/tool.rb +6 -1
  43. data/lib/little_ghost/version.rb +1 -1
  44. data/lib/little_ghost.rb +1 -0
  45. metadata +37 -7
  46. data/lib/little_ghost/mcp/types.rb +0 -216
@@ -1,216 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module LittleGhost
4
- module MCP
5
- module ImmutableValue # :nodoc:
6
- module_function
7
-
8
- def mapping(value, field:)
9
- raise ProtocolError, "MCP #{field} must be an object" unless value.is_a?(Hash)
10
-
11
- freeze_value(DataMap.new(value))
12
- rescue ArgumentError => error
13
- raise ProtocolError, "MCP #{field} is invalid: #{error.message}"
14
- end
15
-
16
- def optional_mapping(value, field:)
17
- return if value.nil?
18
-
19
- mapping(value, field:)
20
- end
21
-
22
- def array(value, field:)
23
- raise ProtocolError, "MCP #{field} must be an array" unless value.is_a?(Array)
24
-
25
- freeze_value(DataMap.new("items" => value).fetch("items"))
26
- rescue ArgumentError => error
27
- raise ProtocolError, "MCP #{field} is invalid: #{error.message}"
28
- end
29
-
30
- def freeze_value(value)
31
- pending = [value]
32
- until pending.empty?
33
- current = pending.pop
34
- case current
35
- when Hash
36
- current.each do |key, child|
37
- key.freeze
38
- pending << child
39
- end
40
- when Array
41
- current.each { |child| pending << child }
42
- end
43
- current.freeze
44
- end
45
- value
46
- end
47
- end
48
-
49
- Definition = Data.define( # :nodoc:
50
- :source_name,
51
- :name,
52
- :description,
53
- :input_schema,
54
- :output_schema,
55
- :annotations,
56
- :title,
57
- :metadata,
58
- :raw
59
- ) do
60
- def initialize(source_name:, name:, description:, input_schema:, raw:, output_schema: nil,
61
- annotations: {}, title: nil, metadata: {})
62
- source_name = String(source_name)
63
- name = String(name)
64
- raise ProtocolError, "MCP tool definition must include a name" if source_name.empty?
65
- raise ConfigurationError, "MCP tool name cannot be empty" if name.empty?
66
-
67
- super(
68
- source_name: source_name.freeze,
69
- name: name.freeze,
70
- description: String(description).freeze,
71
- input_schema: ImmutableValue.mapping(input_schema, field: "tool inputSchema"),
72
- output_schema: ImmutableValue.optional_mapping(output_schema, field: "tool outputSchema"),
73
- annotations: ImmutableValue.mapping(annotations, field: "tool annotations"),
74
- title: title.nil? ? nil : String(title).freeze,
75
- metadata: ImmutableValue.mapping(metadata, field: "tool _meta"),
76
- raw: ImmutableValue.mapping(raw, field: "tool definition")
77
- )
78
- rescue TypeError
79
- raise ProtocolError, "MCP tool definition is invalid"
80
- end
81
-
82
- def [](key) = raw[key]
83
- def fetch(...) = raw.fetch(...)
84
- def dig(...) = raw.dig(...)
85
- def key?(key) = raw.key?(key)
86
- end
87
-
88
- # Immutable server-advertised Tool metadata. +source_name+ is always the
89
- # name sent back to the server; +name+ is the initially generated
90
- # model-facing name and may later be customized on the generated Tool class.
91
- class Definition < Data # :doc:
92
- ##
93
- # :attr_reader: source_name
94
- # The unmodified server-advertised name used for dispatch.
95
-
96
- ##
97
- # :attr_reader: name
98
- # The normalized, optionally prefixed initial model-facing name.
99
-
100
- ##
101
- # :attr_reader: description
102
- # The model-facing description, including LittleGhost's fallback when the
103
- # server omitted one.
104
-
105
- ##
106
- # :attr_reader: input_schema
107
- # The deeply frozen MCP input schema.
108
-
109
- ##
110
- # :attr_reader: output_schema
111
- # The optional deeply frozen MCP output schema.
112
-
113
- ##
114
- # :attr_reader: annotations
115
- # Deeply frozen MCP tool annotations.
116
-
117
- ##
118
- # :attr_reader: title
119
- # The optional human-readable MCP title.
120
-
121
- ##
122
- # :attr_reader: metadata
123
- # Deeply frozen MCP <tt>_meta</tt> object.
124
-
125
- ##
126
- # :attr_reader: raw
127
- # A deeply frozen, indifferent-access copy of the complete server
128
- # definition object.
129
- # Definition also delegates +[]+, +fetch+, +dig+, and +key?+ to this map.
130
- end
131
-
132
- Call = Data.define(:definition, :arguments, :context, :binding) do # :nodoc:
133
- def initialize(definition:, arguments:, context: nil, binding: Tool::Binding.new)
134
- raise ArgumentError, "definition must be a LittleGhost::MCP::Definition" unless definition.is_a?(Definition)
135
-
136
- super(
137
- definition:,
138
- arguments: ImmutableValue.mapping(arguments, field: "tool call arguments"),
139
- context:,
140
- binding:
141
- )
142
- end
143
- end
144
-
145
- # Describes one MCP Tool invocation. It combines the server's Definition,
146
- # the copied arguments, and the Tool::Binding used to execute the generated
147
- # Tool.
148
- class Call < Data # :doc:
149
- ##
150
- # :attr_reader: definition
151
- # The Definition whose +source_name+ is sent to the server.
152
-
153
- ##
154
- # :attr_reader: arguments
155
- # Deeply frozen, indifferent-access arguments.
156
-
157
- ##
158
- # :attr_reader: context
159
- # The RunContext controlling cancellation, deadlines, and working state.
160
-
161
- ##
162
- # :attr_reader: binding
163
- # The Tool::Binding for the generated Tool instance.
164
- end
165
-
166
- Result = Data.define(:content, :structured_content, :error, :metadata, :raw) do # :nodoc:
167
- def initialize(content:, raw:, structured_content: nil, error: false, metadata: {})
168
- unless error.nil? || error == true || error == false
169
- raise ProtocolError, "MCP tool result isError must be boolean"
170
- end
171
-
172
- super(
173
- content: ImmutableValue.array(content, field: "tool result content"),
174
- structured_content: ImmutableValue.optional_mapping(
175
- structured_content,
176
- field: "structuredContent"
177
- ),
178
- error: !!error,
179
- metadata: ImmutableValue.mapping(metadata, field: "tool result _meta"),
180
- raw: ImmutableValue.mapping(raw, field: "tool result")
181
- )
182
- end
183
-
184
- def error? = error
185
- end
186
-
187
- # Represents one MCP Tool result without discarding server fields. Mapping
188
- # callbacks can inspect normalized content, structured content, metadata, or
189
- # the complete protocol value.
190
- class Result < Data # :doc:
191
- ##
192
- # :attr_reader: content
193
- # Deeply frozen MCP content blocks.
194
-
195
- ##
196
- # :attr_reader: structured_content
197
- # The optional deeply frozen structured result object.
198
-
199
- ##
200
- # :attr_reader: error
201
- # Whether the server marked the result with <tt>isError</tt>.
202
-
203
- ##
204
- # :attr_reader: metadata
205
- # Deeply frozen MCP result <tt>_meta</tt>.
206
-
207
- ##
208
- # :attr_reader: raw
209
- # A deeply frozen copy of the complete server result object.
210
-
211
- ##
212
- # :method: error?
213
- # Whether the server marked the result as an error.
214
- end
215
- end
216
- end