doover-js 0.5.1 → 0.5.3
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.
|
@@ -2,6 +2,15 @@ import type { AgentAggregate, BatchMessagesResponse } from "../types/openapi";
|
|
|
2
2
|
import type { RestClient } from "../http/rest-client";
|
|
3
3
|
export interface MultiAgentMessagesParams {
|
|
4
4
|
agent_id: string[];
|
|
5
|
+
/**
|
|
6
|
+
* Per-agent `before` cursors, parallel to `agent_id`. When set, must
|
|
7
|
+
* be the same length as `agent_id`; each agent then uses its own
|
|
8
|
+
* cursor as the upper bound instead of the global `before`. This is
|
|
9
|
+
* how paginating clients should resume from a previous response's
|
|
10
|
+
* `next_cursors` map — each agent paginates independently with no
|
|
11
|
+
* inter-agent "watermark retain".
|
|
12
|
+
*/
|
|
13
|
+
agent_before?: string[];
|
|
5
14
|
before?: string;
|
|
6
15
|
after?: string;
|
|
7
16
|
limit?: number;
|
|
@@ -21,6 +30,14 @@ export declare class AgentsApi {
|
|
|
21
30
|
private readonly rest;
|
|
22
31
|
constructor(rest: RestClient);
|
|
23
32
|
getMultiAgentMessages(channelName: string, params: MultiAgentMessagesParams): Promise<BatchMessagesResponse>;
|
|
33
|
+
/**
|
|
34
|
+
* Batch-fetch aggregates for many agents. Auto-chunks `agent_id` so each
|
|
35
|
+
* request's URL stays under CloudFront's hard 8,192-byte URL quota — each
|
|
36
|
+
* agent contributes ~30 chars (`agent_id=<snowflake>&`), so the chunk size
|
|
37
|
+
* below caps the query string at ~7.5KB even before the base URL. Chunks
|
|
38
|
+
* are fetched in parallel and merged into one `{ results, count }` so
|
|
39
|
+
* callers never have to think about the limit.
|
|
40
|
+
*/
|
|
24
41
|
getMultiAgentAggregates(channelName: string, params: MultiAgentAggregatesParams): Promise<{
|
|
25
42
|
results: AgentAggregate[];
|
|
26
43
|
count: number;
|
package/dist/apis/agents-api.js
CHANGED
|
@@ -13,8 +13,37 @@ class AgentsApi {
|
|
|
13
13
|
results: response.results.map((message) => "timestamp" in message ? message : (0, snowflake_1.addTimestampToMessage)(message)),
|
|
14
14
|
};
|
|
15
15
|
}
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
/**
|
|
17
|
+
* Batch-fetch aggregates for many agents. Auto-chunks `agent_id` so each
|
|
18
|
+
* request's URL stays under CloudFront's hard 8,192-byte URL quota — each
|
|
19
|
+
* agent contributes ~30 chars (`agent_id=<snowflake>&`), so the chunk size
|
|
20
|
+
* below caps the query string at ~7.5KB even before the base URL. Chunks
|
|
21
|
+
* are fetched in parallel and merged into one `{ results, count }` so
|
|
22
|
+
* callers never have to think about the limit.
|
|
23
|
+
*/
|
|
24
|
+
async getMultiAgentAggregates(channelName, params) {
|
|
25
|
+
const path = `/agents/channels/${channelName}/aggregates`;
|
|
26
|
+
const { agent_id, ...rest } = params;
|
|
27
|
+
if (agent_id.length <= MULTI_AGENT_CHUNK_SIZE) {
|
|
28
|
+
return this.rest.get(path, params);
|
|
29
|
+
}
|
|
30
|
+
const chunks = [];
|
|
31
|
+
for (let i = 0; i < agent_id.length; i += MULTI_AGENT_CHUNK_SIZE) {
|
|
32
|
+
chunks.push(agent_id.slice(i, i + MULTI_AGENT_CHUNK_SIZE));
|
|
33
|
+
}
|
|
34
|
+
const responses = await Promise.all(chunks.map((chunk) => this.rest.get(path, {
|
|
35
|
+
...rest,
|
|
36
|
+
agent_id: chunk,
|
|
37
|
+
})));
|
|
38
|
+
return {
|
|
39
|
+
results: responses.flatMap((r) => r.results),
|
|
40
|
+
count: responses.reduce((acc, r) => acc + r.count, 0),
|
|
41
|
+
};
|
|
18
42
|
}
|
|
19
43
|
}
|
|
20
44
|
exports.AgentsApi = AgentsApi;
|
|
45
|
+
// CloudFront fronts the channels-rest API with a hard 8,192-byte URL quota.
|
|
46
|
+
// Each agent adds ~30 chars (`agent_id=<19-digit snowflake>&`), so 250 IDs
|
|
47
|
+
// is ~7.5KB of query string — comfortably under the cap with room for the
|
|
48
|
+
// base URL and any `field_name` params.
|
|
49
|
+
const MULTI_AGENT_CHUNK_SIZE = 250;
|
|
@@ -16,7 +16,12 @@ export interface UseChannelAggregateResult<TData> extends Omit<UseQueryResult<Ag
|
|
|
16
16
|
data: TData | undefined;
|
|
17
17
|
/** Attachments (files/blobs) on the aggregate. */
|
|
18
18
|
attachments: Aggregate["attachments"] | undefined;
|
|
19
|
-
/**
|
|
19
|
+
/**
|
|
20
|
+
* Server timestamp (epoch **milliseconds**) of the last aggregate update —
|
|
21
|
+
* matches the `Date` / `dayjs()` constructor. Use `dayjs(last_updated)`, not
|
|
22
|
+
* `dayjs.unix(last_updated)` (the latter assumes seconds and lands ~58000
|
|
23
|
+
* years in the future).
|
|
24
|
+
*/
|
|
20
25
|
last_updated: number | null | undefined;
|
|
21
26
|
}
|
|
22
27
|
export interface UseChannelAggregateOptions {
|
package/dist/test/apis.test.js
CHANGED
|
@@ -61,6 +61,27 @@ function setupRest(responseFactory) {
|
|
|
61
61
|
(0, chai_1.expect)(fetchMock.getCall(0).args[0]).to.equal("https://api.example.com/agents/channels/c1/messages?agent_id=a1&agent_id=a2&limit=5");
|
|
62
62
|
(0, chai_1.expect)(fetchMock.getCall(1).args[0]).to.equal("https://api.example.com/agents/channels/c1/aggregates?agent_id=a1");
|
|
63
63
|
});
|
|
64
|
+
(0, mocha_1.it)("chunks getMultiAgentAggregates over the per-request agent cap", async () => {
|
|
65
|
+
const { rest, fetchMock } = setupRest((url) => {
|
|
66
|
+
const ids = [...new URL(url).searchParams.getAll("agent_id")];
|
|
67
|
+
return (0, helpers_1.createJsonResponse)({
|
|
68
|
+
results: ids.map((id) => ({ agent_id: id, data: {}, attachments: [] })),
|
|
69
|
+
count: ids.length,
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
const api = new agents_api_1.AgentsApi(rest);
|
|
73
|
+
// 600 agents → 3 chunks of 250/250/100.
|
|
74
|
+
const agentIds = Array.from({ length: 600 }, (_, i) => `a${i}`);
|
|
75
|
+
const aggregates = await api.getMultiAgentAggregates("c1", {
|
|
76
|
+
agent_id: agentIds,
|
|
77
|
+
});
|
|
78
|
+
(0, chai_1.expect)(fetchMock.callCount).to.equal(3);
|
|
79
|
+
(0, chai_1.expect)(aggregates.results).to.have.length(600);
|
|
80
|
+
(0, chai_1.expect)(aggregates.count).to.equal(600);
|
|
81
|
+
// Every agent is represented exactly once across the merged chunks.
|
|
82
|
+
const returnedIds = aggregates.results.map((r) => r.agent_id).sort();
|
|
83
|
+
(0, chai_1.expect)(returnedIds).to.deep.equal([...agentIds].sort());
|
|
84
|
+
});
|
|
64
85
|
(0, mocha_1.it)("covers channels methods", async () => {
|
|
65
86
|
const { rest, fetchMock } = setupRest(() => (0, helpers_1.createJsonResponse)([]));
|
|
66
87
|
const api = new channels_api_1.ChannelsApi(rest);
|
package/dist/types/openapi.d.ts
CHANGED
|
@@ -4,16 +4,28 @@ export interface BatchMessagesResponse {
|
|
|
4
4
|
results: MessageStructure[];
|
|
5
5
|
count: number;
|
|
6
6
|
/**
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
7
|
+
* Per-agent resume cursors. Any agent whose fetch hit the per-agent
|
|
8
|
+
* limit before draining the window maps to the oldest snowflake we
|
|
9
|
+
* did return for it. Pass these back as `agent_before` (parallel to
|
|
10
|
+
* `agent_id`) on the next call to continue paginating each agent
|
|
11
|
+
* independently. Empty (or missing, on older servers) when the
|
|
12
|
+
* window is fully drained.
|
|
13
|
+
*
|
|
14
|
+
* This is the canonical pagination signal — `next` and
|
|
15
|
+
* `at_limit_agent_ids` are kept populated for back-compat with
|
|
16
|
+
* pre-`next_cursors` clients.
|
|
17
|
+
*/
|
|
18
|
+
next_cursors?: Record<string, string>;
|
|
19
|
+
/**
|
|
20
|
+
* Legacy single-cursor pagination signal — the most-recent (highest
|
|
21
|
+
* snowflake id) oldest-cursor across at-limit agents. Clients
|
|
22
|
+
* paginating with a single global `before` should pass this back;
|
|
23
|
+
* prefer `next_cursors` + `agent_before` for new code.
|
|
10
24
|
*/
|
|
11
25
|
next?: string | null;
|
|
12
26
|
/**
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
* Pass these (and only these) as `agent_id` on the next sub-page so the
|
|
16
|
-
* server doesn't repeat DDB lookups for already-exhausted agents.
|
|
27
|
+
* Legacy: agent ids with possibly-more older messages — equivalent to
|
|
28
|
+
* `Object.keys(next_cursors)`. Prefer `next_cursors` for new code.
|
|
17
29
|
*/
|
|
18
30
|
at_limit_agent_ids?: string[];
|
|
19
31
|
}
|