@mastra/client-js 1.46.1-alpha.0 → 1.47.0-alpha.10

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 (60) hide show
  1. package/dist/_types/@ai-sdk_provider/package.json +1 -0
  2. package/dist/_types/@ai-sdk_provider-utils/dist/index.d.ts +1 -1
  3. package/dist/_types/@ai-sdk_provider-utils/package.json +1 -0
  4. package/dist/_types/@ai-sdk_ui-utils/dist/index.d.ts +3 -3
  5. package/dist/_types/@ai-sdk_ui-utils/package.json +1 -0
  6. package/dist/client.d.ts +57 -208
  7. package/dist/client.d.ts.map +1 -1
  8. package/dist/docs/SKILL.md +2 -2
  9. package/dist/docs/assets/SOURCE_MAP.json +1 -1
  10. package/dist/docs/references/docs-connections-a2a.md +4 -3
  11. package/dist/docs/references/docs-harness-agent-controller.md +4 -2
  12. package/dist/docs/references/reference-client-js-agent-controller.md +77 -16
  13. package/dist/docs/references/reference-client-js-agents.md +25 -0
  14. package/dist/docs/references/reference-client-js-mastra-client.md +1 -1
  15. package/dist/docs/references/reference-client-js-observability.md +104 -5
  16. package/dist/docs/references/reference-observability-tracing-trace-query.md +219 -46
  17. package/dist/index.cjs +353 -165
  18. package/dist/index.cjs.map +1 -1
  19. package/dist/index.d.ts +3 -1
  20. package/dist/index.d.ts.map +1 -1
  21. package/dist/index.js +354 -166
  22. package/dist/index.js.map +1 -1
  23. package/dist/resources/a2a.d.ts +5 -1
  24. package/dist/resources/a2a.d.ts.map +1 -1
  25. package/dist/resources/agent-builder.d.ts +25 -40
  26. package/dist/resources/agent-builder.d.ts.map +1 -1
  27. package/dist/resources/agent-controller.d.ts +5 -4
  28. package/dist/resources/agent-controller.d.ts.map +1 -1
  29. package/dist/resources/agent.d.ts +46 -74
  30. package/dist/resources/agent.d.ts.map +1 -1
  31. package/dist/resources/base.d.ts.map +1 -1
  32. package/dist/resources/channels.d.ts +13 -31
  33. package/dist/resources/channels.d.ts.map +1 -1
  34. package/dist/resources/conversations.d.ts +6 -3
  35. package/dist/resources/conversations.d.ts.map +1 -1
  36. package/dist/resources/mcp-tool.d.ts +9 -6
  37. package/dist/resources/mcp-tool.d.ts.map +1 -1
  38. package/dist/resources/memory-thread.d.ts +11 -11
  39. package/dist/resources/memory-thread.d.ts.map +1 -1
  40. package/dist/resources/observability-route-types.d.ts +101 -0
  41. package/dist/resources/observability-route-types.d.ts.map +1 -0
  42. package/dist/resources/observability.d.ts +35 -8
  43. package/dist/resources/observability.d.ts.map +1 -1
  44. package/dist/resources/responses.d.ts +5 -2
  45. package/dist/resources/responses.d.ts.map +1 -1
  46. package/dist/resources/run.d.ts +21 -57
  47. package/dist/resources/run.d.ts.map +1 -1
  48. package/dist/resources/tool.d.ts +6 -4
  49. package/dist/resources/tool.d.ts.map +1 -1
  50. package/dist/resources/vector.d.ts +5 -10
  51. package/dist/resources/vector.d.ts.map +1 -1
  52. package/dist/resources/workflow.d.ts +8 -10
  53. package/dist/resources/workflow.d.ts.map +1 -1
  54. package/dist/route-types.generated.d.ts +3010 -2190
  55. package/dist/route-types.generated.d.ts.map +1 -1
  56. package/dist/types.d.ts +252 -1593
  57. package/dist/types.d.ts.map +1 -1
  58. package/dist/utils/index.d.ts +7 -0
  59. package/dist/utils/index.d.ts.map +1 -1
  60. package/package.json +10 -10
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "1.46.1-alpha.0",
2
+ "version": "1.47.0-alpha.10",
3
3
  "package": "@mastra/client-js",
4
4
  "exports": {},
5
5
  "modules": {}
@@ -67,7 +67,7 @@ Mastra supports A2A Protocol v0.3 and v1.0 on the same agent card and execution
67
67
  - `1.0`: Uses the v1.0 API.
68
68
  - Any other value: Returns a `VersionNotSupported` protocol error.
69
69
 
70
- Existing `A2AAgent` and `MastraClient.getA2A()` integrations continue to use v0.3. Use `MastraClient.getA2AV1()` for v1.0 requests. The v1 client sends `A2A-Version: 1.0` automatically and adds the `tasks/list` operation.
70
+ `A2AAgent` and `MastraClient.getA2A()` use v0.3 by default. Set `protocolVersion: '1.0'` on `A2AAgent` for v1.0 subagent delegation, or use `MastraClient.getA2AV1()` for direct v1.0 requests. Both v1.0 clients send `A2A-Version: 1.0` automatically. The direct v1.0 client also adds the `tasks/list` operation.
71
71
 
72
72
  Import v1.0 protocol types and codecs from `@mastra/core/a2a/v1`. The existing `@mastra/core/a2a/client` export remains on v0.3.
73
73
 
@@ -82,7 +82,7 @@ Use `A2AAgent` when another Mastra agent should delegate work to a remote agent.
82
82
 
83
83
  ## Consume A2A agents as subagents
84
84
 
85
- Use `A2AAgent` to wrap a remote A2A agent, then add it to a parent agent with the [supervisor agents](https://mastra.ai/docs/subagents) pattern. Pass an explicit agent card URL when the remote server hosts multiple agents or uses a custom well-known path.
85
+ Use `A2AAgent` to wrap a remote A2A agent, then add it to a parent agent with the [supervisor agents](https://mastra.ai/docs/subagents) pattern. Pass an explicit agent card URL when the remote server hosts multiple agents or uses a custom well-known path. Set `protocolVersion: '1.0'` when the remote agent requires A2A v1.0. Omit it to use v0.3.
86
86
 
87
87
  ```typescript
88
88
  import { Agent } from '@mastra/core/agent'
@@ -90,6 +90,7 @@ import { A2AAgent } from '@mastra/core/a2a'
90
90
 
91
91
  const remoteWeatherAgent = new A2AAgent({
92
92
  url: 'https://weather.example.com/api/.well-known/weather-agent/agent-card.json',
93
+ protocolVersion: '1.0',
93
94
  headers: {
94
95
  Authorization: `Bearer ${process.env.WEATHER_AGENT_TOKEN}`,
95
96
  },
@@ -112,7 +113,7 @@ During execution, `A2AAgent`:
112
113
 
113
114
  - Fetches and caches the remote agent card.
114
115
  - Reads the execution URL and capabilities from the card.
115
- - Calls `message/send` for non-streaming runs or `message/stream` when streaming is supported.
116
+ - Calls `message/send` or `message/stream` with v0.3, and `SendMessage` or `SendStreamingMessage` with v1.0.
116
117
  - Converts remote messages, tasks, artifacts, and status updates into Mastra subagent results.
117
118
  - Supports `resumeGenerate()` and `resumeStream()` when the remote task requires follow-up input or resubscription.
118
119
 
@@ -74,8 +74,8 @@ const session = await controller.createSession({
74
74
  })
75
75
 
76
76
  const unsubscribe = session.subscribe(event => {
77
- if (event.type === 'message_update') {
78
- console.log(event.message)
77
+ if (event.type === 'message_update' && event.event.type === 'text-delta') {
78
+ process.stdout.write(event.event.delta)
79
79
  }
80
80
  })
81
81
 
@@ -83,6 +83,8 @@ await session.sendMessage({ content: 'Plan a small TypeScript CLI.' })
83
83
  unsubscribe()
84
84
  ```
85
85
 
86
+ Each message emits a `message_start` event with the initial message, zero or more `message_update` events with compact deltas, and a `message_end` event containing the message ID. Apply updates by ID when you need to reconstruct the complete message.
87
+
86
88
  Use the same controller for many Sessions. Don't store a current Session on the controller or route work through controller-level message methods.
87
89
 
88
90
  ## Understand the runtime model
@@ -219,31 +219,92 @@ Returns: `Promise<SendNotificationResult>`
219
219
 
220
220
  `onEvent` receives every event the session emits, discriminated by `event.type`:
221
221
 
222
- | Group | Events |
223
- | ------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
224
- | Run | `agent_start`, `agent_end`, `usage_update`, `goal_evaluation`, `follow_up_queued` |
225
- | Messages | `message_start`, `message_update`, `message_end` |
226
- | Tools | `tool_input_start`, `tool_input_delta`, `tool_input_end`, `tool_start`, `tool_update`, `tool_end`, `shell_output`, `command_exit`, `tool_approval_required`, `tool_suspended`, `tool_suspension_cancelled`, `task_updated` |
227
- | Session | `state_changed`, `display_state_changed`, `mode_changed`, `model_changed`, `thread_changed`, `thread_created`, `thread_deleted`, `thread_title_updated` |
228
- | Subagents | `subagent_start`, `subagent_text_delta`, `subagent_tool_start`, `subagent_tool_end`, `subagent_end`, `subagent_model_changed` |
229
- | Memory | `om_observation_start`, `om_observation_end`, `om_observation_failed`, `om_reflection_start`, `om_reflection_end`, `om_reflection_failed`, `om_buffering_start`, `om_buffering_end`, `om_buffering_failed`, `om_model_changed`, `om_activation`, `om_status`, `om_thread_title_updated` |
230
- | Workspace | `workspace_ready`, `workspace_error`, `workspace_status_changed` |
231
- | Notification | `notification`, `notification_summary`, `info`, `error` |
222
+ | Group | Events |
223
+ | ----------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
224
+ | Run | `agent_start`, `agent_end`, `usage_update`, `goal_evaluation`, `follow_up_queued` |
225
+ | Messages | `message_start`, `message_update`, `message_end` |
226
+ | Tools | `tool_input_start`, `tool_input_delta`, `tool_input_end`, `tool_start`, `tool_update`, `tool_end`, `shell_output`, `command_exit`, `tool_approval_required`, `tool_suspended`, `tool_suspension_cancelled`, `task_updated` |
227
+ | Session | `state_changed`, `display_state_changed`, `mode_changed`, `model_changed`, `thread_changed`, `thread_created`, `thread_deleted`, `thread_title_updated` |
228
+ | Subagents | `subagent_start`, `subagent_text_delta`, `subagent_tool_start`, `subagent_tool_end`, `subagent_end`, `subagent_model_changed` |
229
+ | Memory | `om_observation_start`, `om_observation_end`, `om_observation_failed`, `om_reflection_start`, `om_reflection_end`, `om_reflection_failed`, `om_buffering_start`, `om_buffering_end`, `om_buffering_failed`, `om_model_changed`, `om_activation`, `om_status`, `om_thread_title_updated` |
230
+ | Workspace | `workspace_ready`, `workspace_error`, `workspace_status_changed` |
231
+ | Diagnostics | `info`, `error` |
232
232
 
233
- `message_*` events carry a `MastraDBMessage` and `thread_created` carries a thread, with timestamps hydrated to `Date`.
233
+ Notifications are delivered as agent signals carried on messages rather than as `notification` or `notification_summary` controller events.
234
234
 
235
- A controller can also emit events the SDK doesn't type. `AgentControllerEvent` is the union of `KnownAgentControllerEvent` and `OtherAgentControllerEvent`. Because `OtherAgentControllerEvent.type` is `string`, comparing `event.type` to a literal doesn't narrow the union. Narrow with `isKnownAgentControllerEvent(event)` first:
235
+ A message lifecycle uses three event shapes:
236
+
237
+ - `message_start` carries the initial `MastraDBMessage`, with `createdAt` hydrated to `Date`.
238
+ - `message_update` carries the message `id` and a compact text, reasoning, or part update.
239
+ - `message_end` carries the `id` of the completed message.
240
+
241
+ `thread_created` also carries a thread with timestamps hydrated to `Date`.
242
+
243
+ A controller can emit events the SDK doesn't type. `AgentControllerEvent` is the union of `KnownAgentControllerEvent` and `OtherAgentControllerEvent`. Because `OtherAgentControllerEvent.type` is `string`, comparing `event.type` to a literal doesn't narrow the union. Narrow with `isKnownAgentControllerEvent(event)` first, then reconstruct messages by ID:
236
244
 
237
245
  ```typescript
238
- import { isKnownAgentControllerEvent } from '@mastra/client-js'
246
+ import {
247
+ isKnownAgentControllerEvent,
248
+ type AgentControllerEvent,
249
+ type KnownAgentControllerEvent,
250
+ type MastraDBMessage,
251
+ } from '@mastra/client-js'
252
+
253
+ type MessageUpdate = Extract<KnownAgentControllerEvent, { type: 'message_update' }>['event']
254
+
255
+ const activeMessages = new Map<string, MastraDBMessage>()
256
+
257
+ function applyUpdate(message: MastraDBMessage, update: MessageUpdate): MastraDBMessage {
258
+ const parts = [...message.content.parts]
259
+
260
+ if (update.type === 'text-delta') {
261
+ const index = parts.findLastIndex(part => part.type === 'text')
262
+ const part = parts[index]
263
+
264
+ if (part?.type === 'text') {
265
+ parts[index] = { ...part, text: part.text + update.delta }
266
+ } else {
267
+ parts.push({ type: 'text', text: update.delta })
268
+ }
269
+ } else if (update.type === 'reasoning-delta') {
270
+ const part = parts[update.index]
271
+ const reasoning = part?.type === 'reasoning' ? part.reasoning + update.delta : update.delta
272
+ parts[update.index] = {
273
+ ...(part?.type === 'reasoning' ? part : { type: 'reasoning' as const }),
274
+ reasoning,
275
+ details: [{ type: 'text', text: reasoning }],
276
+ }
277
+ } else {
278
+ parts[update.index] = update.part
279
+ }
280
+
281
+ return { ...message, content: { ...message.content, parts } }
282
+ }
239
283
 
240
284
  function handleEvent(event: AgentControllerEvent) {
241
285
  if (!isKnownAgentControllerEvent(event)) return
242
286
 
243
287
  switch (event.type) {
244
- case 'message_update':
245
- render(event.message)
288
+ case 'message_start':
289
+ activeMessages.set(event.message.id, structuredClone(event.message))
290
+ break
291
+ case 'message_update': {
292
+ const message = activeMessages.get(event.id)
293
+ if (!message) break
294
+
295
+ const updated = applyUpdate(message, event.event)
296
+ activeMessages.set(event.id, updated)
297
+ render(updated)
298
+ break
299
+ }
300
+ case 'message_end': {
301
+ const message = activeMessages.get(event.id)
302
+ if (!message) break
303
+
304
+ renderComplete(message)
305
+ activeMessages.delete(event.id)
246
306
  break
307
+ }
247
308
  case 'tool_approval_required':
248
309
  showApproval(event.toolCallId)
249
310
  break
@@ -251,7 +312,7 @@ function handleEvent(event: AgentControllerEvent) {
251
312
  }
252
313
  ```
253
314
 
254
- Use `agentControllerMessageText(message)` to pull the plain text out of a message's nested content parts.
315
+ Use `agentControllerMessageText(message)` to pull the plain text out of a reconstructed message's nested content parts.
255
316
 
256
317
  ## Related
257
318
 
@@ -119,6 +119,31 @@ while (true) {
119
119
  }
120
120
  ```
121
121
 
122
+ #### Cancelling a stream
123
+
124
+ Cancelling the response body aborts the underlying HTTP request and stops any pending client tool executions and follow-up requests:
125
+
126
+ ```typescript
127
+ const response = await agent.stream('Tell me a story')
128
+ const reader = response.body.getReader()
129
+
130
+ const { value } = await reader.read()
131
+ await reader.cancel('user navigated away')
132
+ ```
133
+
134
+ You can also pass an `abortSignal` to cancel from outside the stream. The same option is available on `streamUntilIdle()`, `resumeStream()`, `resumeStreamUntilIdle()`, `approveToolCall()`, `declineToolCall()`, `generate()`, and `generateLegacy()`:
135
+
136
+ ```typescript
137
+ const controller = new AbortController()
138
+
139
+ const response = await agent.stream('Tell me a story', {
140
+ abortSignal: controller.signal,
141
+ })
142
+
143
+ // Later
144
+ controller.abort()
145
+ ```
146
+
122
147
  #### AI SDK compatible format
123
148
 
124
149
  To stream AI SDK-formatted parts on the client from an `agent.stream(...)` response, wrap `response.processDataStream` into a `ReadableStream<ChunkType>` and use `toAISdkStream`:
@@ -81,7 +81,7 @@ You can also pass `requestContext` as a `Record<string, any>`.
81
81
 
82
82
  **getWorkflow(workflowId)** (`Workflow`): Retrieves a specific workflow instance by ID.
83
83
 
84
- **getAgentBuilderActions()** (`Promise<Record<string, WorkflowInfo>>`): Returns all available Agent Builder actions. See Agent Builder API.
84
+ **getAgentBuilderActions()** (`Promise<GetAgentBuilderActionsResponse>`): Returns all available Agent Builder actions. See Agent Builder API.
85
85
 
86
86
  **getAgentBuilderAction(actionId)** (`AgentBuilder`): Retrieves an Agent Builder action by ID. See Agent Builder API.
87
87
 
@@ -65,9 +65,9 @@ const selected = await mastraClient.getTrace(list.spans[0].traceId)
65
65
 
66
66
  It accepts the same filtering, ordering and delta-polling arguments as `listTraces()`. Use `listTraces()` when you actually need the full span payloads.
67
67
 
68
- ## Querying traces with recursive predicates
68
+ ## Querying traces and threads
69
69
 
70
- `queryTraces()` finds completed logical traces using trace fields and conditions over related spans or scores. Every query requires an ISO timestamp range of at most 31 days.
70
+ `queryTraces()` finds completed logical traces using trace fields and conditions over related spans, scores, or feedback. Every query requires an ISO timestamp range of at most 31 days.
71
71
 
72
72
  ```typescript
73
73
  const result = await mastraClient.queryTraces({
@@ -89,9 +89,106 @@ const result = await mastraClient.queryTraces({
89
89
  })
90
90
  ```
91
91
 
92
- The API limits predicate depth, nodes, related clauses, set members, literal bytes, and the total literal budget before storage execution. Cursor pages are deterministic but aren't a database snapshot, so signals written between requests can change later pages. PostgreSQL and ClickHouse queries have a configurable 15-second execution timeout.
92
+ ### Discover trace-query fields and values
93
93
 
94
- See [Advanced trace queries](https://mastra.ai/reference/observability/tracing/trace-query) for the complete limits, request fields, predicates, grouping, cursor pagination, response shapes, and errors.
94
+ `getTraceQueryFields()` returns canonical query fields and observed top-level string metadata fields for one predicate scope. The response includes each field's value kind, supported operators, and whether value suggestions are available.
95
+
96
+ ```typescript
97
+ const timeRange = {
98
+ from: '2026-08-01T00:00:00.000Z',
99
+ to: '2026-08-08T00:00:00.000Z',
100
+ }
101
+
102
+ const fields = await mastraClient.getTraceQueryFields({
103
+ timeRange,
104
+ predicateScope: 'spans',
105
+ search: 'model',
106
+ limit: 25,
107
+ })
108
+ ```
109
+
110
+ Use the exact local path from the response with the same scope. For example, `model` belongs inside a `spans.some` or `spans.none` predicate. A global picker can fetch `trace`, `spans`, `scores`, and `feedback` in parallel.
111
+
112
+ Call `getTraceQueryValues()` only after selecting a field with `valueSuggestions: true`:
113
+
114
+ ```typescript
115
+ const controller = new AbortController()
116
+
117
+ const values = await mastraClient.getTraceQueryValues(
118
+ {
119
+ timeRange,
120
+ predicateScope: 'spans',
121
+ path: 'model',
122
+ search: 'claude',
123
+ limit: 25,
124
+ },
125
+ { signal: controller.signal },
126
+ )
127
+
128
+ // Cancel this autocomplete request when the search text changes.
129
+ controller.abort()
130
+ ```
131
+
132
+ Both methods use case-insensitive literal substring search and accept an empty search. Limits default to 25 and can't exceed 100. Results are ordered by occurrence count, then deterministically by path or value. `observedFieldsTruncated` and `valuesTruncated` indicate that the caller should refine `search`. Discovery has no cursor.
133
+
134
+ Suggestions are bounded and advisory. Manual values remain valid when suggestions are unavailable, empty, or truncated. Discovery doesn't accept a draft query predicate. The client doesn't retry either request, and a per-call `AbortSignal` takes precedence over the signal configured on `MastraClient`.
135
+
136
+ A discovery timeout rejects with `504 TRACE_QUERY_EXECUTION_TIMEOUT`. Backend memory or resource exhaustion rejects with `503 TRACE_QUERY_RESOURCE_LIMIT`. Neither error contains partial suggestions; truncation flags are only present on successful, completely ranked responses.
137
+
138
+ `queryTraceThreads()` returns thread identities derived from observability traces after applying eligibility and cross-trace conditions. It doesn't read or return memory thread records or messages. In this example, one production trace can have the low factuality score while another production trace in the same thread has the clinician correction:
139
+
140
+ ```typescript
141
+ const result = await mastraClient.queryTraceThreads({
142
+ traces: {
143
+ timeRange: {
144
+ from: '2026-08-01T00:00:00.000Z',
145
+ to: '2026-08-08T00:00:00.000Z',
146
+ },
147
+ where: {
148
+ op: 'eq',
149
+ left: { path: 'environment' },
150
+ right: { literal: 'production' },
151
+ },
152
+ },
153
+ where: {
154
+ op: 'and',
155
+ args: [
156
+ {
157
+ traces: {
158
+ some: {
159
+ scores: {
160
+ some: {
161
+ op: 'lt',
162
+ left: { path: 'score' },
163
+ right: { literal: 0.6 },
164
+ },
165
+ },
166
+ },
167
+ },
168
+ },
169
+ {
170
+ traces: {
171
+ some: {
172
+ feedback: {
173
+ some: {
174
+ op: 'eq',
175
+ left: { path: 'feedbackType' },
176
+ right: { literal: 'clinician-correction' },
177
+ },
178
+ },
179
+ },
180
+ },
181
+ },
182
+ ],
183
+ },
184
+ })
185
+
186
+ // { threads: [{ threadId: 'thread-123' }], page: { next: null } }
187
+ ```
188
+
189
+ The API limits predicate depth, nodes, related clauses, set members, literal bytes, and the total literal budget before storage execution. Cursor pages are deterministic but aren't a database snapshot, so signals written between requests can change later pages. PostgreSQL and ClickHouse trace and thread queries have a configurable 15-second execution timeout.
190
+
191
+ See [Advanced trace queries](https://mastra.ai/reference/observability/tracing/trace-query) for the complete limits, request fields, predicates, thread qualification semantics, cursor pagination, response shapes, and errors.
95
192
 
96
193
  ## Deleting traces
97
194
 
@@ -136,7 +233,7 @@ const scores = await mastraClient.listScoresBySpan({
136
233
 
137
234
  ## Feedback
138
235
 
139
- Feedback methods create, list, and query human-in-the-loop signals such as ratings, thumbs, comments, and corrections through the target Mastra runtime and its configured observability storage. They don't call the hosted Mastra Platform feedback query API. See the [feedback guide](https://mastra.ai/docs/observability/feedback) for examples and the [feedback reference](https://mastra.ai/reference/observability/feedback) for full schemas.
236
+ Feedback methods create, list, and query human-in-the-loop signals such as ratings, thumbs, comments, and corrections through the target Mastra runtime and its configured observability storage. They don't call the hosted Mastra Platform Feedback API. See the [feedback guide](https://mastra.ai/docs/observability/feedback) for examples and the [feedback reference](https://mastra.ai/reference/observability/feedback) for full schemas.
140
237
 
141
238
  ### Creating feedback
142
239
 
@@ -188,6 +285,8 @@ const feedback = await mastraClient.listFeedback({
188
285
  })
189
286
  ```
190
287
 
288
+ `filters` accepts every [`FeedbackFilter`](https://mastra.ai/reference/observability/feedback) field. The client sends them as query parameters on `GET /api/observability/feedback`. See [list query parameters](https://mastra.ai/reference/observability/feedback).
289
+
191
290
  ### Aggregating feedback
192
291
 
193
292
  Aggregate numeric feedback values, such as ratings or thumbs encoded as `1` and `-1`: