mistri 0.5.0 → 0.6.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 +469 -4
- data/CONTRIBUTING.md +52 -0
- data/README.md +289 -385
- data/SECURITY.md +40 -0
- data/UPGRADING.md +640 -0
- data/assets/logo-animated.svg +30 -0
- data/assets/logo-dark.svg +14 -0
- data/assets/logo-light.svg +14 -0
- data/assets/logo.svg +14 -0
- data/assets/social-preview.png +0 -0
- data/docs/README.md +87 -0
- data/docs/context-and-workspaces.md +378 -0
- data/docs/mcp.md +366 -0
- data/docs/reliability.md +450 -0
- data/docs/sessions.md +295 -0
- data/docs/sub-agents.md +401 -0
- data/docs/tool-contracts.md +324 -0
- data/examples/approval.rb +36 -0
- data/examples/browser.rb +27 -0
- data/examples/page_editor.rb +31 -0
- data/examples/quickstart.rb +21 -0
- data/lib/generators/mistri/install/install_generator.rb +7 -3
- data/lib/generators/mistri/install/templates/migration.rb.tt +2 -2
- data/lib/generators/mistri/mcp/templates/migration.rb.tt +1 -1
- data/lib/generators/mistri/mcp/templates/model.rb.tt +15 -8
- data/lib/mistri/agent.rb +575 -55
- data/lib/mistri/budget.rb +26 -1
- data/lib/mistri/child.rb +72 -16
- data/lib/mistri/compaction.rb +26 -10
- data/lib/mistri/compactor.rb +34 -11
- data/lib/mistri/console.rb +28 -7
- data/lib/mistri/content.rb +9 -3
- data/lib/mistri/dispatchers.rb +14 -12
- data/lib/mistri/errors.rb +83 -4
- data/lib/mistri/event.rb +24 -8
- data/lib/mistri/event_delivery.rb +60 -0
- data/lib/mistri/locks.rb +3 -3
- data/lib/mistri/mcp/client.rb +74 -19
- data/lib/mistri/mcp/egress.rb +216 -0
- data/lib/mistri/mcp/oauth.rb +476 -127
- data/lib/mistri/mcp/wires.rb +115 -23
- data/lib/mistri/mcp.rb +42 -8
- data/lib/mistri/message.rb +21 -11
- data/lib/mistri/models.rb +160 -22
- data/lib/mistri/providers/anthropic/assembler.rb +282 -44
- data/lib/mistri/providers/anthropic/serializer.rb +14 -9
- data/lib/mistri/providers/anthropic.rb +29 -6
- data/lib/mistri/providers/fake.rb +26 -10
- data/lib/mistri/providers/gemini/assembler.rb +148 -21
- data/lib/mistri/providers/gemini/serializer.rb +78 -9
- data/lib/mistri/providers/gemini.rb +31 -5
- data/lib/mistri/providers/openai/assembler.rb +337 -60
- data/lib/mistri/providers/openai/serializer.rb +13 -12
- data/lib/mistri/providers/openai.rb +29 -5
- data/lib/mistri/providers/schema_capabilities.rb +214 -0
- data/lib/mistri/result.rb +1 -1
- data/lib/mistri/schema.rb +893 -75
- data/lib/mistri/session.rb +560 -48
- data/lib/mistri/sinks/coalesced.rb +17 -10
- data/lib/mistri/spawner.rb +111 -61
- data/lib/mistri/sse.rb +57 -14
- data/lib/mistri/stores/active_record.rb +1 -1
- data/lib/mistri/stores/memory.rb +21 -2
- data/lib/mistri/sub_agent/execution.rb +81 -0
- data/lib/mistri/sub_agent/runtime.rb +297 -0
- data/lib/mistri/sub_agent.rb +124 -87
- data/lib/mistri/task_output.rb +24 -6
- data/lib/mistri/tool.rb +93 -13
- data/lib/mistri/tool_arguments.rb +377 -0
- data/lib/mistri/tool_call.rb +43 -9
- data/lib/mistri/tool_context.rb +4 -2
- data/lib/mistri/tool_executor.rb +117 -26
- data/lib/mistri/tool_result.rb +15 -10
- data/lib/mistri/tools/edit_file.rb +62 -8
- data/lib/mistri/tools.rb +41 -4
- data/lib/mistri/transport.rb +149 -44
- data/lib/mistri/usage.rb +65 -13
- data/lib/mistri/version.rb +1 -1
- data/lib/mistri/workspace/active_record.rb +183 -3
- data/lib/mistri/workspace/directory.rb +28 -8
- data/lib/mistri/workspace/memory.rb +34 -9
- data/lib/mistri/workspace/single.rb +62 -5
- data/lib/mistri/workspace.rb +39 -0
- data/lib/mistri.rb +6 -1
- data/mistri.gemspec +34 -0
- metadata +31 -3
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Mistri
|
|
4
|
+
module Providers
|
|
5
|
+
# Keeps provider grammar limits out of task semantics: an incompatible
|
|
6
|
+
# native schema is omitted while the prompt and local validator stay exact.
|
|
7
|
+
module SchemaCapabilities
|
|
8
|
+
COMMON_KEYWORDS = %w[
|
|
9
|
+
type properties required additionalProperties items enum description
|
|
10
|
+
].freeze
|
|
11
|
+
PROFILES = {
|
|
12
|
+
openai: {
|
|
13
|
+
keywords: COMMON_KEYWORDS,
|
|
14
|
+
root_object: true,
|
|
15
|
+
boolean_schemas: false,
|
|
16
|
+
tuple_arrays: false,
|
|
17
|
+
all_properties_required: true,
|
|
18
|
+
max_depth: 10,
|
|
19
|
+
max_properties: 5000,
|
|
20
|
+
max_enum_values: 1000,
|
|
21
|
+
max_schema_string_chars: 120_000,
|
|
22
|
+
large_enum_threshold: 250,
|
|
23
|
+
max_large_enum_chars: 15_000
|
|
24
|
+
}.freeze,
|
|
25
|
+
anthropic: {
|
|
26
|
+
keywords: COMMON_KEYWORDS,
|
|
27
|
+
root_object: false,
|
|
28
|
+
boolean_schemas: false,
|
|
29
|
+
tuple_arrays: false,
|
|
30
|
+
all_properties_required: false,
|
|
31
|
+
max_optional_properties: 24
|
|
32
|
+
}.freeze,
|
|
33
|
+
gemini: {
|
|
34
|
+
keywords: (COMMON_KEYWORDS + %w[prefixItems format title]).freeze,
|
|
35
|
+
root_object: false,
|
|
36
|
+
boolean_schemas: true,
|
|
37
|
+
tuple_arrays: true,
|
|
38
|
+
all_properties_required: false,
|
|
39
|
+
formats: %w[date-time date time].freeze,
|
|
40
|
+
enum_types: %w[string number integer].freeze
|
|
41
|
+
}.freeze
|
|
42
|
+
}.freeze
|
|
43
|
+
|
|
44
|
+
module_function
|
|
45
|
+
|
|
46
|
+
def derive(schema, provider)
|
|
47
|
+
profile = PROFILES.fetch(provider)
|
|
48
|
+
state = { properties: 0, optional_properties: 0, enum_values: 0,
|
|
49
|
+
schema_string_chars: 0 }
|
|
50
|
+
return unless supported?(schema, profile, state, root: true, depth: 0)
|
|
51
|
+
|
|
52
|
+
schema
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def supported?(schema, profile, state, depth:, root: false)
|
|
56
|
+
return profile[:boolean_schemas] if schema.equal?(true) || schema.equal?(false)
|
|
57
|
+
return false unless schema_shape_supported?(schema, profile)
|
|
58
|
+
|
|
59
|
+
type = schema["type"]
|
|
60
|
+
depth += 1 if %w[object array].include?(type)
|
|
61
|
+
return false unless node_supported?(schema, type, profile, state, root:, depth:)
|
|
62
|
+
|
|
63
|
+
child_schemas(schema).all? do |member|
|
|
64
|
+
supported?(member, profile, state, depth:)
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
private_class_method :supported?
|
|
68
|
+
|
|
69
|
+
def schema_shape_supported?(schema, profile)
|
|
70
|
+
schema.is_a?(Hash) && schema["type"].is_a?(String) &&
|
|
71
|
+
(schema.keys - profile[:keywords]).empty?
|
|
72
|
+
end
|
|
73
|
+
private_class_method :schema_shape_supported?
|
|
74
|
+
|
|
75
|
+
def node_supported?(schema, type, profile, state, root:, depth:)
|
|
76
|
+
within_limit?(depth, profile[:max_depth]) &&
|
|
77
|
+
root_supported?(type, profile, root) &&
|
|
78
|
+
object_supported?(schema, type, profile, state) &&
|
|
79
|
+
array_supported?(schema, type, profile) &&
|
|
80
|
+
format_supported?(schema, type, profile) &&
|
|
81
|
+
enum_supported?(schema, type, profile, state)
|
|
82
|
+
end
|
|
83
|
+
private_class_method :node_supported?
|
|
84
|
+
|
|
85
|
+
def root_supported?(type, profile, root)
|
|
86
|
+
!root || !profile[:root_object] || type == "object"
|
|
87
|
+
end
|
|
88
|
+
private_class_method :root_supported?
|
|
89
|
+
|
|
90
|
+
def object_supported?(schema, type, profile, state)
|
|
91
|
+
object_keywords = schema.key?("properties") || schema.key?("required") ||
|
|
92
|
+
schema.key?("additionalProperties")
|
|
93
|
+
return true unless type == "object" || object_keywords
|
|
94
|
+
return false unless valid_object_shape?(schema, type)
|
|
95
|
+
|
|
96
|
+
properties = schema["properties"]
|
|
97
|
+
required = schema["required"]
|
|
98
|
+
return false unless required_properties_declared?(required, properties)
|
|
99
|
+
|
|
100
|
+
required_index = required.to_h { |name| [name, true] }
|
|
101
|
+
record_object_limits(state, properties, required_index)
|
|
102
|
+
return false unless object_limits_supported?(state, profile)
|
|
103
|
+
return true unless profile[:all_properties_required]
|
|
104
|
+
|
|
105
|
+
required_index.length == properties.length &&
|
|
106
|
+
required_index.each_key.all? { |key| properties.key?(key) }
|
|
107
|
+
end
|
|
108
|
+
private_class_method :object_supported?
|
|
109
|
+
|
|
110
|
+
def required_properties_declared?(required, properties)
|
|
111
|
+
required.all? { |name| properties.key?(name) }
|
|
112
|
+
end
|
|
113
|
+
private_class_method :required_properties_declared?
|
|
114
|
+
|
|
115
|
+
def valid_object_shape?(schema, type)
|
|
116
|
+
type == "object" && schema["properties"].is_a?(Hash) &&
|
|
117
|
+
schema["required"].is_a?(Array) && schema["additionalProperties"] == false
|
|
118
|
+
end
|
|
119
|
+
private_class_method :valid_object_shape?
|
|
120
|
+
|
|
121
|
+
def record_object_limits(state, properties, required)
|
|
122
|
+
state[:properties] += properties.length
|
|
123
|
+
state[:optional_properties] += properties.keys.count { |name| !required.key?(name) }
|
|
124
|
+
state[:schema_string_chars] += properties.keys.sum(&:length)
|
|
125
|
+
end
|
|
126
|
+
private_class_method :record_object_limits
|
|
127
|
+
|
|
128
|
+
def object_limits_supported?(state, profile)
|
|
129
|
+
within_limit?(state[:properties], profile[:max_properties]) &&
|
|
130
|
+
within_limit?(state[:optional_properties], profile[:max_optional_properties]) &&
|
|
131
|
+
within_limit?(state[:schema_string_chars], profile[:max_schema_string_chars])
|
|
132
|
+
end
|
|
133
|
+
private_class_method :object_limits_supported?
|
|
134
|
+
|
|
135
|
+
def array_supported?(schema, type, profile)
|
|
136
|
+
array_keywords = schema.key?("items") || schema.key?("prefixItems")
|
|
137
|
+
return true unless type == "array" || array_keywords
|
|
138
|
+
return false unless type == "array"
|
|
139
|
+
return false if schema.key?("prefixItems") && !profile[:tuple_arrays]
|
|
140
|
+
return false unless schema.key?("items") || schema.key?("prefixItems")
|
|
141
|
+
|
|
142
|
+
true
|
|
143
|
+
end
|
|
144
|
+
private_class_method :array_supported?
|
|
145
|
+
|
|
146
|
+
def format_supported?(schema, type, profile)
|
|
147
|
+
return true unless schema.key?("format")
|
|
148
|
+
|
|
149
|
+
profile[:formats]&.include?(schema["format"]) && type == "string"
|
|
150
|
+
end
|
|
151
|
+
private_class_method :format_supported?
|
|
152
|
+
|
|
153
|
+
def enum_supported?(schema, type, profile, state)
|
|
154
|
+
values = schema["enum"]
|
|
155
|
+
return true unless values
|
|
156
|
+
return false unless values.is_a?(Array)
|
|
157
|
+
return false unless enum_domain_supported?(values, type, profile)
|
|
158
|
+
|
|
159
|
+
state[:enum_values] += values.length
|
|
160
|
+
strings = values.grep(String)
|
|
161
|
+
string_chars = strings.sum(&:length)
|
|
162
|
+
state[:schema_string_chars] += string_chars
|
|
163
|
+
return false unless within_limit?(state[:enum_values], profile[:max_enum_values])
|
|
164
|
+
return false unless within_limit?(state[:schema_string_chars],
|
|
165
|
+
profile[:max_schema_string_chars])
|
|
166
|
+
|
|
167
|
+
threshold = profile[:large_enum_threshold]
|
|
168
|
+
return true unless threshold && values.length > threshold && strings.length == values.length
|
|
169
|
+
|
|
170
|
+
string_chars <= profile[:max_large_enum_chars]
|
|
171
|
+
end
|
|
172
|
+
private_class_method :enum_supported?
|
|
173
|
+
|
|
174
|
+
def enum_domain_supported?(values, type, profile)
|
|
175
|
+
return true unless profile[:enum_types]
|
|
176
|
+
return false unless profile[:enum_types].include?(type)
|
|
177
|
+
|
|
178
|
+
case type
|
|
179
|
+
when "string" then values.all?(String)
|
|
180
|
+
when "number" then values.all? { |value| finite_number?(value) }
|
|
181
|
+
when "integer" then values.all? { |value| integer_number?(value) }
|
|
182
|
+
end
|
|
183
|
+
end
|
|
184
|
+
private_class_method :enum_domain_supported?
|
|
185
|
+
|
|
186
|
+
def finite_number?(value)
|
|
187
|
+
value.is_a?(Integer) || (value.is_a?(Float) && value.finite?)
|
|
188
|
+
end
|
|
189
|
+
private_class_method :finite_number?
|
|
190
|
+
|
|
191
|
+
def integer_number?(value)
|
|
192
|
+
value.is_a?(Integer) || (value.is_a?(Float) && value.finite? && value.to_i == value)
|
|
193
|
+
end
|
|
194
|
+
private_class_method :integer_number?
|
|
195
|
+
|
|
196
|
+
def within_limit?(value, limit)
|
|
197
|
+
!limit || value <= limit
|
|
198
|
+
end
|
|
199
|
+
private_class_method :within_limit?
|
|
200
|
+
|
|
201
|
+
def child_schemas(schema)
|
|
202
|
+
children = schema.fetch("properties", {}).values
|
|
203
|
+
items = schema["items"]
|
|
204
|
+
children << items if items.is_a?(Hash) || items.equal?(true) || items.equal?(false)
|
|
205
|
+
children.concat(schema.fetch("prefixItems", []))
|
|
206
|
+
additional = schema["additionalProperties"]
|
|
207
|
+
children << additional if additional.is_a?(Hash) || additional.equal?(true)
|
|
208
|
+
children
|
|
209
|
+
end
|
|
210
|
+
private_class_method :child_schemas
|
|
211
|
+
end
|
|
212
|
+
private_constant :SchemaCapabilities
|
|
213
|
+
end
|
|
214
|
+
end
|
data/lib/mistri/result.rb
CHANGED
|
@@ -10,7 +10,7 @@ module Mistri
|
|
|
10
10
|
# Reads delegate to the final message, so result.text works whether the run
|
|
11
11
|
# completed or suspended.
|
|
12
12
|
# output is a task's validated value, nil on plain runs. usage is the
|
|
13
|
-
# run's own accounting: every
|
|
13
|
+
# run's own accounting: every provider attempt plus compaction calls, summed
|
|
14
14
|
# (a resumed run counts from the resume; task sums across its fix passes).
|
|
15
15
|
# handed_off marks a run that ended because an ends_turn tool executed:
|
|
16
16
|
# complete, but the final word was the tool's, and whatever comes next (a
|