Module: Parse::Agent::ResultFormatter

Extended by:
ResultFormatter
Included in:
ResultFormatter
Defined in:
lib/parse/agent/result_formatter.rb

Overview

The ResultFormatter transforms Parse API responses into LLM-friendly formats that are easy to understand and process.

It provides consistent structure, human-readable type descriptions, and truncates large results to fit context windows.

Constant Summary collapse

MAX_RESULTS_DISPLAY =

Maximum number of results to include in output

50
TYPE_NAMES =

Parse field type mappings for human-readable output

{
  "String" => "string",
  "Number" => "number",
  "Boolean" => "boolean",
  "Date" => "date/time",
  "Object" => "object (JSON)",
  "Array" => "array",
  "GeoPoint" => "geo location",
  "File" => "file",
  "Pointer" => "pointer (reference)",
  "Relation" => "relation (many-to-many)",
  "Bytes" => "binary data",
  "Polygon" => "polygon (geo shape)",
  "ACL" => "access control list",
}.freeze

Instance Method Summary collapse

Instance Method Details

#format_object(class_name, object, truncated_include_fields: nil) ⇒ Hash

Format a single object

Parameters:

  • class_name (String)

    the class name

  • object (Hash)

    the object data

  • truncated_include_fields (Hash, nil) (defaults to: nil)

    map of pointer-name => source: when keys-on-include auto-projection narrowed any joined record.

Returns:

  • (Hash)

    formatted object



212
213
214
215
216
217
218
219
220
221
222
223
224
225
# File 'lib/parse/agent/result_formatter.rb', line 212

def format_object(class_name, object, truncated_include_fields: nil)
  envelope = {
    class_name: class_name,
    object_id: object["objectId"],
    created_at: object["createdAt"],
    updated_at: object["updatedAt"],
    object: simplify_object(object),
  }
  if truncated_include_fields && !truncated_include_fields.empty?
    envelope[:truncated_include_fields] =
      truncated_include_fields.transform_values { |meta| meta[:dropped] }
  end
  envelope
end

#format_query_results(class_name, results, limit:, skip:, where: nil, keys: nil, order: nil, include: nil, truncated_include_fields: nil) ⇒ Hash

Format query results

Parameters:

  • class_name (String)

    the class that was queried

  • results (Array<Hash>)

    array of result objects

  • limit (Integer)

    the limit that was requested

  • skip (Integer)

    the skip offset

  • where (Hash, nil) (defaults to: nil)

    query constraints from the original call

  • keys (Array<String>, nil) (defaults to: nil)

    field projection from the original call

  • order (String, nil) (defaults to: nil)

    sort field from the original call

  • include (Array<String>, nil) (defaults to: nil)

    pointer includes from the original call

Returns:

  • (Hash)

    formatted results



153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
# File 'lib/parse/agent/result_formatter.rb', line 153

def format_query_results(class_name, results, limit:, skip:,
                         where: nil, keys: nil, order: nil, include: nil,
                         truncated_include_fields: nil)
  total = results.size
  truncated = total > MAX_RESULTS_DISPLAY
  has_more = total >= limit

  displayed_results = if truncated
      results.first(MAX_RESULTS_DISPLAY)
    else
      results
    end

  next_call = if has_more
      next_args = {
        class_name: class_name,
        limit: limit,
        skip: skip + limit,
        where: where,
        keys: keys,
        order: order,
        include: include,
      }.compact
      { tool: "query_class", arguments: next_args }
    end

  # Surface keys-on-include auto-projection metadata so the LLM
  # can see which joins were narrowed and re-ask with explicit
  # dotted paths (`keys: ["user.iconImage"]`) if it needs fields
  # that were dropped. Suppress the key when nothing was auto-
  # projected — keeps the envelope minimal for the common case.
  truncated_includes_payload =
    if truncated_include_fields && !truncated_include_fields.empty?
      truncated_include_fields.transform_values { |meta| meta[:dropped] }.compact
    end

  {
    class_name: class_name,
    result_count: total,
    pagination: {
      limit: limit,
      skip: skip,
      has_more: has_more,
    },
    truncated: truncated,
    truncated_note: truncated ? "Showing first #{MAX_RESULTS_DISPLAY} of #{total} results" : nil,
    truncated_include_fields: truncated_includes_payload,
    next_call: next_call,
    results: displayed_results.map { |obj| simplify_object(obj) },
  }.compact
end

#format_schema(schema) ⇒ Hash

Format a single schema for detailed display

Parameters:

  • schema (Hash)

    schema object from Parse (enriched with metadata)

Returns:

  • (Hash)

    formatted schema details



84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
# File 'lib/parse/agent/result_formatter.rb', line 84

def format_schema(schema)
  class_name = schema["className"]
  fields = schema["fields"] || {}
  indexes = schema["indexes"] || {}
  clp = schema["classLevelPermissions"] || {}
  agent_methods = schema["agent_methods"] || []

  result = {
    class_name: class_name,
    type: class_type(class_name),
  }

  # Include class description if present
  result[:description] = schema["description"] if schema["description"]

  # Include analytics usage hint if present (separate from description)
  result[:usage] = schema["usage"] if schema["usage"]

  result[:fields] = format_fields_detailed(fields)
  result[:indexes] = format_indexes(indexes)
  result[:permissions] = format_clp(clp)

  # Include agent methods if any
  result[:agent_methods] = agent_methods if agent_methods.any?

  # Include the canonical "valid state" filter when declared. Lets
  # callers that opt out of the default `apply_canonical_filter`
  # behavior reproduce the predicate manually in their where:.
  if schema["canonical_filter"].is_a?(Hash) && schema["canonical_filter"].any?
    result[:canonical_filter] = schema["canonical_filter"]
  end

  # Echo the wire-format agent_fields allowlist when declared. The
  # allowlist already filters `result[:fields]` by omission, but the
  # explicit list answers "what may I write in `keys:` for this
  # class" without forcing the consumer to scan the fields array.
  # Storage-form columns (`_p_*`) and other Parse-internal
  # underscored columns are never addressable through agent tools.
  if schema["agent_fields"].is_a?(Array) && schema["agent_fields"].any?
    result[:agent_fields] = schema["agent_fields"]
  end

  # Echo the narrower join projection (wire-format) when declared.
  # Tells consumers "when this class is included on another class's
  # query, these are the fields you'll see."
  if schema["agent_join_fields"].is_a?(Array) && schema["agent_join_fields"].any?
    result[:agent_join_fields] = schema["agent_join_fields"]
  end

  # Include relationship edges if any (set by MetadataRegistry)
  if schema["relations"].is_a?(Hash) &&
     (schema["relations"]["outgoing"].to_a.any? || schema["relations"]["incoming"].to_a.any?)
    result[:relations] = schema["relations"]
  end

  result
end

#format_schemas(schemas) ⇒ Hash

Format multiple schemas for display (compact summary) Returns class names grouped by type for efficient token usage. Use get_schema for detailed field info on specific classes.

Parameters:

  • schemas (Array<Hash>)

    array of schema objects from Parse (enriched with metadata)

Returns:

  • (Hash)

    formatted schema summary



41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
# File 'lib/parse/agent/result_formatter.rb', line 41

def format_schemas(schemas)
  built_in = []
  custom = []

  schemas.each do |schema|
    class_name = schema["className"]
    fields = schema["fields"] || {}
    agent_methods = schema["agent_methods"] || []

    # Subtract the four system fields (objectId, createdAt, updatedAt,
    # ACL) when reporting a "user-meaningful" count, but never let the
    # subtraction go negative — the allowlist filter in enriched_schema
    # may have already trimmed system fields out.
    info = {
      name: class_name,
      fields: [fields.size - 4, 0].max,
    }

    # Include description if present (compact)
    info[:desc] = schema["description"] if schema["description"]

    # Include agent methods count if any
    info[:methods] = agent_methods.size if agent_methods.any?

    if class_name.start_with?("_")
      built_in << info
    else
      custom << info
    end
  end

  {
    total: schemas.size,
    note: "Use get_schema(class_name) for detailed field info",
    built_in: built_in,
    custom: custom,
  }
end