@agent-native/dispatch 0.10.3 → 0.11.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 (44) hide show
  1. package/README.md +42 -96
  2. package/dist/actions/delete-staged-dataset.d.ts +8 -0
  3. package/dist/actions/delete-staged-dataset.d.ts.map +1 -0
  4. package/dist/actions/delete-staged-dataset.js +31 -0
  5. package/dist/actions/delete-staged-dataset.js.map +1 -0
  6. package/dist/actions/index.d.ts.map +1 -1
  7. package/dist/actions/index.js +6 -0
  8. package/dist/actions/index.js.map +1 -1
  9. package/dist/actions/list-staged-datasets.d.ts +13 -0
  10. package/dist/actions/list-staged-datasets.d.ts.map +1 -0
  11. package/dist/actions/list-staged-datasets.js +33 -0
  12. package/dist/actions/list-staged-datasets.js.map +1 -0
  13. package/dist/actions/provider-api-catalog.d.ts +1 -1
  14. package/dist/actions/provider-api-catalog.js +1 -1
  15. package/dist/actions/provider-api-catalog.js.map +1 -1
  16. package/dist/actions/provider-api-docs.d.ts +15 -0
  17. package/dist/actions/provider-api-docs.d.ts.map +1 -1
  18. package/dist/actions/provider-api-docs.js +29 -0
  19. package/dist/actions/provider-api-docs.js.map +1 -1
  20. package/dist/actions/provider-api-request.d.ts +14 -1
  21. package/dist/actions/provider-api-request.d.ts.map +1 -1
  22. package/dist/actions/provider-api-request.js +88 -5
  23. package/dist/actions/provider-api-request.js.map +1 -1
  24. package/dist/actions/query-staged-dataset.d.ts +28 -0
  25. package/dist/actions/query-staged-dataset.d.ts.map +1 -0
  26. package/dist/actions/query-staged-dataset.js +105 -0
  27. package/dist/actions/query-staged-dataset.js.map +1 -0
  28. package/dist/server/lib/provider-api.d.ts +4 -5
  29. package/dist/server/lib/provider-api.d.ts.map +1 -1
  30. package/dist/server/lib/provider-api.js +2 -1
  31. package/dist/server/lib/provider-api.js.map +1 -1
  32. package/dist/server/plugins/agent-chat.d.ts.map +1 -1
  33. package/dist/server/plugins/agent-chat.js +2 -0
  34. package/dist/server/plugins/agent-chat.js.map +1 -1
  35. package/package.json +11 -13
  36. package/src/actions/delete-staged-dataset.ts +39 -0
  37. package/src/actions/index.ts +6 -0
  38. package/src/actions/list-staged-datasets.ts +36 -0
  39. package/src/actions/provider-api-catalog.ts +1 -1
  40. package/src/actions/provider-api-docs.ts +39 -0
  41. package/src/actions/provider-api-request.ts +118 -5
  42. package/src/actions/query-staged-dataset.ts +125 -0
  43. package/src/server/lib/provider-api.ts +6 -6
  44. package/src/server/plugins/agent-chat.ts +2 -0
@@ -20,7 +20,7 @@ export default defineAction({
20
20
  return {
21
21
  providers,
22
22
  guidance:
23
- "Workspace integrations and grants are not capability limits. When a provider can answer a question through its HTTP API, inspect docs/spec URLs here and call provider-api-request with the exact provider API method/path/query/body instead of adding a rigid one-off action. Custom providers registered via provider-api-register also appear here.",
23
+ "Workspace integrations and grants are not capability limits. When a provider can answer a question through its HTTP API, inspect docs/spec URLs here and call provider-api-request with the exact provider API method/path/query/body instead of adding a rigid one-off action. For broad searches, joins, classification, or absence claims, stage or save the full bounded corpus and reduce it with query-staged-dataset or run-code. Custom providers registered via provider-api-register also appear here.",
24
24
  };
25
25
  },
26
26
  });
@@ -2,6 +2,22 @@ import { defineAction } from "@agent-native/core";
2
2
  import { z } from "zod";
3
3
  import { fetchProviderApiDocs } from "../server/lib/provider-api.js";
4
4
 
5
+ const BooleanFromQuerySchema = z.preprocess(
6
+ (value) => (typeof value === "string" ? value === "true" : value),
7
+ z.boolean(),
8
+ );
9
+ const WebContentSearchSchema = z.object({
10
+ query: z.union([z.string(), z.array(z.string())]).optional(),
11
+ queries: z.array(z.string()).optional(),
12
+ terms: z.array(z.string()).optional(),
13
+ regex: z.string().optional(),
14
+ regexFlags: z.string().optional(),
15
+ source: z.enum(["extracted", "raw"]).optional(),
16
+ maxMatches: z.coerce.number().int().min(1).max(500).optional(),
17
+ contextChars: z.coerce.number().int().min(0).max(1_000).optional(),
18
+ caseSensitive: BooleanFromQuerySchema.optional(),
19
+ });
20
+
5
21
  export default defineAction({
6
22
  description:
7
23
  "Inspect provider API docs/spec metadata, or fetch ANY public API documentation page, OpenAPI spec, changelog, or web page. Registered docs/spec URLs from provider-api-catalog are curated starting points, but any public https/http URL is allowed. Use web-search to find documentation URLs first when uncertain, then fetch them here. SSRF guard still applies — private/internal addresses are blocked.",
@@ -26,6 +42,29 @@ export default defineAction({
26
42
  .max(4 * 1024 * 1024)
27
43
  .optional()
28
44
  .describe("Maximum response bytes to read. Default 1MB, max 4MB."),
45
+ maxChars: z.coerce
46
+ .number()
47
+ .int()
48
+ .min(1)
49
+ .max(200_000)
50
+ .optional()
51
+ .describe("Maximum extracted content characters to return."),
52
+ responseMode: z
53
+ .enum(["auto", "raw", "text", "markdown", "links", "metadata", "matches"])
54
+ .optional()
55
+ .describe(
56
+ "How to return fetched docs. Default auto extracts HTML to markdown; use matches with search for compact snippets.",
57
+ ),
58
+ extract: z
59
+ .enum(["readability", "all-visible", "none"])
60
+ .optional()
61
+ .describe("HTML extraction strategy. Default readability."),
62
+ includeLinks: BooleanFromQuerySchema.optional().describe(
63
+ "Include compact links from extracted HTML. Default true.",
64
+ ),
65
+ search: WebContentSearchSchema.optional().describe(
66
+ "Optional post-fetch search over extracted content by default. Supports query, queries, terms, regex, source, maxMatches, and contextChars.",
67
+ ),
29
68
  }),
30
69
  http: { method: "GET" },
31
70
  readOnly: true,
@@ -1,9 +1,69 @@
1
1
  import { defineAction } from "@agent-native/core";
2
+ import { stagingExecuteRequest } from "@agent-native/core/provider-api/staging";
3
+ import { getCredentialContext } from "@agent-native/core/server/request-context";
2
4
  import { z } from "zod";
3
- import { executeProviderApiRequest } from "../server/lib/provider-api.js";
5
+ import {
6
+ DISPATCH_APP_ID,
7
+ executeProviderApiRequest,
8
+ } from "../server/lib/provider-api.js";
4
9
 
5
10
  const MethodSchema = z.enum(["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD"]);
6
11
 
12
+ const PaginationSchema = z
13
+ .object({
14
+ nextCursorPath: z
15
+ .string()
16
+ .optional()
17
+ .describe(
18
+ "Dot-path in the response JSON where the next cursor/token lives, e.g. 'next_cursor', 'meta.next', or 'nextPageToken'.",
19
+ ),
20
+ cursorParam: z
21
+ .string()
22
+ .optional()
23
+ .describe(
24
+ "Query parameter name to inject the cursor into the next request. Use cursorBodyPath for APIs that page through POST bodies.",
25
+ ),
26
+ cursorBodyPath: z
27
+ .string()
28
+ .optional()
29
+ .describe(
30
+ "Dot-path in the JSON request body to set to the next cursor. Use this for POST-body pagination.",
31
+ ),
32
+ pageParam: z
33
+ .string()
34
+ .optional()
35
+ .describe(
36
+ "Use page-number mode: this query param is incremented on each page.",
37
+ ),
38
+ startPage: z.coerce
39
+ .number()
40
+ .int()
41
+ .optional()
42
+ .describe("Starting page number for pageParam mode (default 1)."),
43
+ offsetParam: z
44
+ .string()
45
+ .optional()
46
+ .describe(
47
+ "Use offset mode: this query param is incremented by pageSize on each request.",
48
+ ),
49
+ pageSize: z.coerce
50
+ .number()
51
+ .int()
52
+ .min(1)
53
+ .optional()
54
+ .describe(
55
+ "Expected page size for offset increments. Defaults to the actual item count of the first page.",
56
+ ),
57
+ maxPages: z.coerce
58
+ .number()
59
+ .int()
60
+ .min(1)
61
+ .max(200)
62
+ .optional()
63
+ .describe("Maximum pages to fetch server-side (default 50, max 200)."),
64
+ })
65
+ .optional();
66
+
7
67
  export default defineAction({
8
68
  description:
9
69
  "Make an arbitrary authenticated HTTP request to a shared workspace integration, configured provider API, or custom provider registered via provider-api-register. Use this as the flexible escape hatch when Dispatch needs a provider endpoint, filter, pagination mode, payload, or API version that no canned action models. The request is constrained to the provider host, uses configured credentials automatically, blocks private/internal URLs, and redacts secrets from responses.",
@@ -77,8 +137,24 @@ export default defineAction({
77
137
  .string()
78
138
  .optional()
79
139
  .describe(
80
- "Workspace file path to save the full response body to instead of returning it in context (e.g. 'analysis/hubspot-deals.json'). When set, returns only a compact summary {savedTo, status, bytes, preview} and allows up to 20MB response. Useful for large datasets that would overflow context.",
140
+ "Workspace file path to save the full response body to instead of returning it in context (e.g. 'analysis/provider-response.json'). When set, returns only a compact summary {savedTo, status, bytes, preview} and allows up to 20MB response. Useful for large datasets that would overflow context.",
141
+ ),
142
+ stageAs: z
143
+ .string()
144
+ .min(1)
145
+ .optional()
146
+ .describe(
147
+ "When set, parse the response as an array of records and write them into a staged dataset with this name. Returns a compact summary instead of the raw body. Re-staging the same name replaces the previous dataset.",
148
+ ),
149
+ itemsPath: z
150
+ .string()
151
+ .optional()
152
+ .describe(
153
+ "Dot-path to the items array in the response JSON, e.g. 'items', 'results', or 'data'. Omit for auto-detection.",
81
154
  ),
155
+ pagination: PaginationSchema.describe(
156
+ "Pagination config for server-side fetchAll when stageAs is set. Supports cursor (nextCursorPath + cursorParam or cursorBodyPath), page, and offset modes.",
157
+ ),
82
158
  fetchAllPages: z
83
159
  .object({
84
160
  cursorPath: z
@@ -88,8 +164,15 @@ export default defineAction({
88
164
  ),
89
165
  cursorParam: z
90
166
  .string()
167
+ .optional()
168
+ .describe(
169
+ "Query parameter name to pass the cursor on subsequent pages, e.g. 'cursor' or 'page_token'. Use cursorBodyPath instead for APIs that put cursors in POST bodies.",
170
+ ),
171
+ cursorBodyPath: z
172
+ .string()
173
+ .optional()
91
174
  .describe(
92
- "Query parameter name to pass the cursor on subsequent pages, e.g. 'cursor' or 'page_token'.",
175
+ "Dot-path in the JSON request body to set to the next cursor. Use for POST-body pagination.",
93
176
  ),
94
177
  itemsPath: z
95
178
  .string()
@@ -109,9 +192,39 @@ export default defineAction({
109
192
  })
110
193
  .optional()
111
194
  .describe(
112
- "Enable cursor-based pagination. After each response, reads cursorPath from the JSON body and re-issues the request with cursorParam set, accumulating items from itemsPath (or whole bodies) until cursor is empty or maxPages is reached. Combine with saveToFile to write the full dataset to a workspace file.",
195
+ "Enable cursor-based pagination. After each response, reads cursorPath from the JSON body and re-issues the request with cursorParam or cursorBodyPath set, accumulating items from itemsPath (or whole bodies) until cursor is empty or maxPages is reached. Combine with saveToFile to write the full dataset to a workspace file.",
113
196
  ),
114
197
  }),
115
198
  http: false,
116
- run: async (args) => executeProviderApiRequest(args),
199
+ run: async (args) => {
200
+ if (args.stageAs) {
201
+ const ctx = getCredentialContext();
202
+ if (!ctx) {
203
+ throw new Error("No authenticated context for provider API staging.");
204
+ }
205
+ return stagingExecuteRequest(
206
+ {
207
+ provider: args.provider,
208
+ method: args.method,
209
+ path: args.path,
210
+ query: args.query,
211
+ headers: args.headers,
212
+ body: args.body,
213
+ auth: args.auth,
214
+ connectionId: args.connectionId,
215
+ accountId: args.accountId,
216
+ timeoutMs: args.timeoutMs,
217
+ maxBytes: args.maxBytes,
218
+ stageAs: args.stageAs,
219
+ itemsPath: args.itemsPath,
220
+ pagination: args.pagination,
221
+ },
222
+ (reqArgs) => executeProviderApiRequest(reqArgs),
223
+ { appId: DISPATCH_APP_ID, ownerEmail: ctx.userEmail },
224
+ );
225
+ }
226
+ return executeProviderApiRequest(
227
+ args as unknown as Parameters<typeof executeProviderApiRequest>[0],
228
+ );
229
+ },
117
230
  });
@@ -0,0 +1,125 @@
1
+ import { defineAction } from "@agent-native/core";
2
+ import { runAggregateQuery } from "@agent-native/core/provider-api/staged-datasets-aggregate";
3
+ import {
4
+ getStagedDatasetMeta,
5
+ getStagedDatasetRows,
6
+ } from "@agent-native/core/provider-api/staged-datasets-store";
7
+ import { getCredentialContext } from "@agent-native/core/server/request-context";
8
+ import { z } from "zod";
9
+ import { DISPATCH_APP_ID } from "../server/lib/provider-api.js";
10
+
11
+ const WhereSchema = z.object({
12
+ column: z.string().min(1),
13
+ op: z.enum([
14
+ "equals",
15
+ "not_equals",
16
+ "contains",
17
+ "not_contains",
18
+ "gt",
19
+ "gte",
20
+ "lt",
21
+ "lte",
22
+ "exists",
23
+ "not_exists",
24
+ ]),
25
+ value: z.unknown().optional(),
26
+ });
27
+
28
+ const AggregateFieldSchema = z.object({
29
+ column: z.string().min(1).describe("Column to aggregate."),
30
+ op: z
31
+ .enum(["sum", "avg", "count", "min", "max", "count_distinct"])
32
+ .describe("Aggregation function."),
33
+ as: z
34
+ .string()
35
+ .optional()
36
+ .describe("Output column name. Default: {op}_{column}."),
37
+ });
38
+
39
+ export default defineAction({
40
+ description:
41
+ "Run a filter/aggregate/project query over a staged dataset previously written by provider-api-request (stageAs). Use after staging provider records, messages, documents, issues, events, or search results to count, group, filter, or project rows without re-fetching provider APIs.",
42
+ schema: z.object({
43
+ datasetId: z
44
+ .string()
45
+ .min(1)
46
+ .describe(
47
+ "Dataset id from provider-api-request stageAs result, or from list-staged-datasets.",
48
+ ),
49
+ where: z
50
+ .array(WhereSchema)
51
+ .optional()
52
+ .describe(
53
+ "Row-level filters (AND). Ops: equals, not_equals, contains, not_contains, gt, gte, lt, lte, exists, not_exists.",
54
+ ),
55
+ groupBy: z
56
+ .array(z.string().min(1))
57
+ .optional()
58
+ .describe(
59
+ "Column(s) to group by. Omit for a single aggregate over all rows.",
60
+ ),
61
+ aggregate: z
62
+ .array(AggregateFieldSchema)
63
+ .optional()
64
+ .describe(
65
+ "Aggregation operations. When set, non-group columns are aggregated. Omit to return raw rows.",
66
+ ),
67
+ select: z
68
+ .array(z.string().min(1))
69
+ .optional()
70
+ .describe("Column projection when aggregate is empty."),
71
+ orderBy: z.string().optional().describe("Sort output by this column."),
72
+ orderDir: z
73
+ .enum(["asc", "desc"])
74
+ .optional()
75
+ .describe("Sort direction (default asc)."),
76
+ limit: z.coerce
77
+ .number()
78
+ .int()
79
+ .min(1)
80
+ .max(10_000)
81
+ .optional()
82
+ .describe("Maximum rows to return (default all, max 10000)."),
83
+ }),
84
+ http: false,
85
+ readOnly: true,
86
+ run: async (args) => {
87
+ const ctx = getCredentialContext();
88
+ if (!ctx) {
89
+ throw new Error("No authenticated context for query-staged-dataset.");
90
+ }
91
+
92
+ const meta = await getStagedDatasetMeta({
93
+ id: args.datasetId,
94
+ appId: DISPATCH_APP_ID,
95
+ ownerEmail: ctx.userEmail,
96
+ });
97
+ if (!meta) {
98
+ throw new Error(
99
+ `Dataset ${args.datasetId} not found (or belongs to a different owner/app).`,
100
+ );
101
+ }
102
+
103
+ const rows = await getStagedDatasetRows({
104
+ id: args.datasetId,
105
+ appId: DISPATCH_APP_ID,
106
+ ownerEmail: ctx.userEmail,
107
+ });
108
+
109
+ const result = runAggregateQuery(rows, {
110
+ where: args.where,
111
+ groupBy: args.groupBy,
112
+ aggregate: args.aggregate,
113
+ select: args.select,
114
+ orderBy: args.orderBy,
115
+ orderDir: args.orderDir,
116
+ limit: args.limit,
117
+ });
118
+
119
+ return {
120
+ dataset: { id: meta.id, name: meta.name, totalRows: meta.rowCount },
121
+ rowCount: result.length,
122
+ rows: result,
123
+ };
124
+ },
125
+ });
@@ -1,6 +1,7 @@
1
1
  import {
2
2
  PROVIDER_API_IDS,
3
3
  createProviderApiRuntime,
4
+ type ProviderApiDocsOptions,
4
5
  listCustomProviders,
5
6
  type ProviderApiId,
6
7
  type ProviderApiMethod,
@@ -8,6 +9,7 @@ import {
8
9
  } from "@agent-native/core/provider-api";
9
10
  import { getCredentialContext } from "@agent-native/core/server";
10
11
 
12
+ export const DISPATCH_APP_ID = "dispatch";
11
13
  export const DISPATCH_PROVIDER_API_IDS = PROVIDER_API_IDS;
12
14
  export type DispatchProviderApiId = ProviderApiId;
13
15
  export type { ProviderApiMethod, ProviderApiRequestArgs };
@@ -23,7 +25,7 @@ function requireCtx(action: string) {
23
25
  }
24
26
 
25
27
  const runtime = createProviderApiRuntime({
26
- appId: "dispatch",
28
+ appId: DISPATCH_APP_ID,
27
29
  localCredentialSource: "dispatch_local",
28
30
  getCredentialContext: () => requireCtx("requests"),
29
31
  getCustomProviders: async () => {
@@ -56,11 +58,9 @@ export function listProviderApiCatalog(provider?: string) {
56
58
  return runtime.listCatalog(provider);
57
59
  }
58
60
 
59
- export function fetchProviderApiDocs(options: {
60
- provider: string;
61
- url?: string;
62
- maxBytes?: number;
63
- }) {
61
+ export function fetchProviderApiDocs(
62
+ options: ProviderApiDocsOptions & { provider: string },
63
+ ) {
64
64
  return runtime.fetchDocs(options);
65
65
  }
66
66
 
@@ -15,6 +15,7 @@ export default createAgentChatPlugin({
15
15
  // a build-time-generated `.generated/actions-registry.ts` (the latter is a
16
16
  // template-only construct that the Vite plugin emits next to actions/).
17
17
  actions: dispatchActions,
18
+ codeExecution: { production: "sandboxed" },
18
19
  systemPrompt: `You are the central dispatch for this workspace.
19
20
 
20
21
  Default posture:
@@ -33,6 +34,7 @@ Use the standard workspace primitives:
33
34
  - If the starter template is used, treat it as scaffolding only: the finished app must be branded as the requested app with its own home screen/navigation/package metadata/manifest, and must not leave visible "Starter", "Blank app", or "New app" UI behind.
34
35
  - Treat first-party apps such as Mail, Calendar, Analytics, Brain, Assets, and Dispatch as existing hosted/connected neighbors available through links and A2A/default connected agents. Do not create wrapper apps, child apps, nested routes, or cloned template copies just to give a new app access to them; build only the genuinely new workflow and delegate cross-app work to those existing apps.
35
36
  - Integration grants are not provider capability limits. For ad hoc provider inspection, querying, reporting, or troubleshooting, call provider-api-catalog/provider-api-docs, then provider-api-request against the provider's real HTTP API. Use connectionId for a specific shared grant and accountId for a specific OAuth account. Never expose secret values or silently widen app access while doing this.
37
+ - For broad provider searches, joins, classification, corpus counts, or absence claims, fetch every relevant page or an explicitly bounded cohort, stage/save large responses with stageAs/saveToFile/fetchAllPages, and reduce them with query-staged-dataset or run-code. Report source, filters, row counts, pagination, truncation, failed pages, and uncovered gaps.
36
38
 
37
39
  When a user asks for something like a digest, reminder, routing rule, or saved behavior:
38
40
  - First decide whether it should be a resource, a recurring job, a destination, or a delegated task.