@hydradb/mcp 1.1.0 → 1.2.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.
package/CHANGELOG.md ADDED
@@ -0,0 +1,167 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project are documented here.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [1.2.0] - 2026-08-14
9
+
10
+ ### ⚠️ Migration required if you call the old tool names
11
+
12
+ The seven deprecated `hydra_db_*` tool aliases are **no longer registered by
13
+ default**. If your `mcp.json` still calls them, add one environment variable:
14
+
15
+ ```json
16
+ "env": { "HYDRADB_MCP_LEGACY_TOOLS": "1" }
17
+ ```
18
+
19
+ Everything else in this release is backward compatible. This is called out first
20
+ because a minor version bump under-signals it: the change is breaking for anyone
21
+ who has not migrated to the canonical names.
22
+
23
+ The reason is not only the ~3,500 tokens per conversation the alias manifest
24
+ costs (44% of it). The alias names are systematically better literal matches for
25
+ how users phrase requests than the canonical names — "search my memory" matches
26
+ `hydra_db_search` exactly while `hydradb_query` needs a synonym step — and
27
+ picking one costs real capability, since `hydra_db_ingest_conversation` cannot
28
+ set `kind`, `overwrite`, `title`, `infer` or `is_markdown`.
29
+
30
+ Migration table:
31
+
32
+ | Deprecated | Use instead |
33
+ |---|---|
34
+ | `hydra_db_search` | `hydradb_query` |
35
+ | `hydra_db_store`, `hydra_db_ingest_conversation` | `hydradb_ingest` |
36
+ | `hydra_db_list_memories`, `hydra_db_list_sources` | `hydradb_list` |
37
+ | `hydra_db_fetch_content` | `hydradb_inspect` |
38
+ | `hydra_db_delete_memory` | `hydradb_delete` |
39
+
40
+ ### Other breaking changes
41
+
42
+ - `kind` is now **required** on `hydradb_list`. It previously defaulted to
43
+ `memory`, so `hydradb_list({})` returned memories only and read as the complete
44
+ inventory — a caller asking "what does Hydra DB have?" never saw the knowledge
45
+ corpus that `hydradb_query` searches by default.
46
+ - `hydradb_query` now returns a single context block instead of a summary
47
+ followed by full context. Every chunk body was previously sent twice.
48
+ - `hydradb_query` defaults to `detail: "compact"`, which trims each chunk body to
49
+ ~600 characters and omits surrounding-context blocks. Pass `detail: "full"` for
50
+ the previous rendering.
51
+
52
+ ### Added
53
+
54
+ - **`hydradb_status`** — check whether ingested sources have finished indexing.
55
+ Ingestion is asynchronous, so a query issued immediately after a save can
56
+ legitimately return nothing; this distinguishes "still indexing" from "the save
57
+ failed".
58
+ - **Pagination** on `hydradb_list` (`page`, `page_size`), with the response
59
+ stating how much of the corpus it covered.
60
+ - **`kind` on `hydradb_ingest`** — knowledge can now be created, not just
61
+ searched, listed, inspected and deleted.
62
+ - **Bulk delete** — `hydradb_delete` accepts `ids`, reporting partial removals
63
+ as partial.
64
+ - **New query parameters**: `source_ids` (search inside specific documents),
65
+ `metadata_filters`, `num_related_chunks`, `operator`, `mode: "auto"`,
66
+ `detail`.
67
+ - **New ingest parameters**: `metadata`, `observation_date`, `overwrite`.
68
+ - **`offset`/`limit`/`expiry_seconds`** on `hydradb_inspect`.
69
+ - **`structuredContent`** on `hydradb_list`, `hydradb_ingest` and
70
+ `hydradb_delete`, alongside the existing text.
71
+ - **Lifecycle handling** — SIGINT/SIGTERM drain in-flight tool calls before
72
+ closing; unhandled rejections and uncaught exceptions are logged and exit
73
+ non-zero.
74
+ - **New environment variables**: `HYDRADB_TIMEOUT_SECONDS` (default 30),
75
+ `HYDRADB_MAX_RETRIES` (default 2), `HYDRADB_MCP_LEGACY_TOOLS` (default off).
76
+
77
+ ### Fixed
78
+
79
+ Most of these returned a success-shaped result while losing or inventing data,
80
+ so neither the caller nor the user learned anything had gone wrong.
81
+
82
+ - **Nothing ever emitted an ID.** Query results carried no value that
83
+ `hydradb_inspect` or `hydradb_delete` would accept, and ingest returned a
84
+ preview of the caller's own text instead of the id the server assigned. Recall
85
+ and follow-up could not compose, and correcting a stored memory was
86
+ unreachable in both directions.
87
+ - **A delete that removed nothing reported "not found or already deleted"** — a
88
+ cause never observed, and the reassuring one. A caller that guessed an id read
89
+ it as confirmation that the user's data was gone.
90
+ - **The memory listing presented page one as the entire store.** With 4,000
91
+ memories stored, it answered "50 memories:" and page 2 was unreachable through
92
+ the MCP entirely. The source listing printed the corpus-wide total above a
93
+ single page.
94
+ - **Per-item ingest failures were discarded.** The server reports which item
95
+ failed and why; the tool reported bare counts, so the only recovery was to
96
+ re-ingest everything — which, since a reused `source_id` replaces, could
97
+ destroy the item that had succeeded.
98
+ - **Generated conversation ids collided.** `mcp-conversation-${Date.now()}` has
99
+ millisecond resolution, and with `upsert` hardcoded on, a collision silently
100
+ replaced the earlier conversation and reported success.
101
+ - **`hydradb_list` ignored `source_ids` for memories** — accepted, validated, and
102
+ dropped in silence.
103
+ - **Raw JSON envelopes reached the prompt.** When ingest stored the serialised
104
+ source record, the renderer emitted ids and tenant identifiers where the
105
+ content should have been.
106
+ - **Graph relations went missing.** `source_chunk_ids` — the primary
107
+ chunk-to-relation mapping — was dropped by the response adapter, so relations
108
+ linked only that way were never rendered.
109
+ - **`mode: "url"` could not work.** `presignedUrl` was never read, so the one
110
+ mode whose purpose is a download link returned "(no text content)".
111
+ - **`hydradb_inspect` could end a session.** Binary content was inlined as
112
+ base64, so a 1 MB scanned PDF became roughly 350k tokens in one call. Binary is
113
+ now never inlined, and text output is bounded.
114
+ - **Error bodies were unbounded and unfiltered.** A CDN or proxy error page
115
+ reached the caller whole. Now structured-first, capped, and scrubbed of
116
+ credential-shaped material.
117
+ - **No timeout was configured**, so a failing endpoint could occupy a caller for
118
+ ~3 minutes; and `extra.signal` was never forwarded, so a cancelled tool call
119
+ left the request in flight.
120
+ - **Failures were inconsistently flagged.** A server-*refused* delete and a
121
+ failed inspect returned success-shaped results, so a client branching on
122
+ `isError` misread them.
123
+ - **Every untitled note was titled "MCP Memory"**, and title is the only
124
+ per-chunk label rendered in search results.
125
+ - **`npm test` could pass having run zero tests** on Node 18 and 20 — `/bin/sh`
126
+ does not expand `**`, and CI was running a different command.
127
+
128
+ ### Changed
129
+
130
+ - Tool descriptions now say **when** to call each tool — recall before
131
+ answering, save what the user reveals — with worked examples. Nothing
132
+ previously did, which is the difference between a memory product and a
133
+ note-taking tool.
134
+ - Parameter descriptions explain the decision each informs rather than restating
135
+ the type.
136
+ - All four MCP behaviour hints (`readOnlyHint`, `destructiveHint`,
137
+ `idempotentHint`, `openWorldHint`) are declared on every tool. `destructiveHint`
138
+ could not previously be set at all, and the spec defaults it to *true* for
139
+ non-readonly tools — so `hydradb_ingest` read as destructive.
140
+ - Overlapping chunks whose content is wholly contained in another are suppressed,
141
+ and extra context is deduplicated by content rather than id.
142
+ - `max_results` now bounds what is rendered, not just what is requested.
143
+ - The recall renderer reads SDK types directly; the snake_case mirror and its
144
+ adapters are gone (−112 lines).
145
+ - Ingest input is bounded (1M characters, 500 turns).
146
+ - `moduleResolution` is `NodeNext`, so the SDK's `exports` subpaths resolve.
147
+ - The `lint` CI job runs an actual linter; the publish workflow smoke-tests the
148
+ built package and asserts the tarball contents.
149
+
150
+ ## [1.1.1] - 2026-07
151
+
152
+ ### Fixed
153
+
154
+ - `hydradb_query` no longer pins `kind: "memory"`, which had made every ingested
155
+ knowledge source unreachable from the MCP.
156
+ - The server reports its real version instead of a hardcoded `1.0.0`.
157
+
158
+ ## [1.1.0] - 2026-07
159
+
160
+ ### Added
161
+
162
+ - Canonical HydraDB tool vocabulary (`hydradb_*`), with the previous `hydra_db_*`
163
+ names kept as deprecated aliases.
164
+
165
+ [1.2.0]: https://github.com/hydra-db/hydradb-mcp/releases/tag/v1.2.0
166
+ [1.1.1]: https://github.com/hydra-db/hydradb-mcp/releases/tag/v1.1.1
167
+ [1.1.0]: https://github.com/hydra-db/hydradb-mcp/releases/tag/v1.1.0
package/README.md CHANGED
@@ -4,72 +4,127 @@ MCP (Model Context Protocol) server for [Hydra DB](https://hydradb.com), the sta
4
4
 
5
5
  ## Available Tools
6
6
 
7
- The tool names follow the canonical HydraDB vocabulary. Every previous name still
8
- works as a **deprecated alias** (marked as such in its description) so existing
9
- `mcp.json` files keep working but new integrations should use the canonical
10
- names below.
7
+ | Tool | What it does |
8
+ |---|---|
9
+ | `hydradb_query` | Search memories and knowledge together, with knowledge-graph context |
10
+ | `hydradb_ingest` | Save a note, a document, or a conversation |
11
+ | `hydradb_list` | Enumerate one family — every memory, or every knowledge source |
12
+ | `hydradb_inspect` | Fetch one source's full content by id |
13
+ | `hydradb_delete` | Remove one or more items by id, irreversibly |
14
+ | `hydradb_status` | Check whether an ingested source has finished indexing |
15
+
16
+ Ids flow between these: `hydradb_query` and `hydradb_list` emit them;
17
+ `hydradb_inspect`, `hydradb_delete` and `hydradb_status` accept them.
18
+
19
+ ### Deprecated aliases
20
+
21
+ The previous `hydra_db_*` tool names are **no longer registered by default** as
22
+ of 1.2.0. If your `mcp.json` still calls them, set:
11
23
 
12
- | Canonical tool | Deprecated alias(es) |
24
+ ```
25
+ HYDRADB_MCP_LEGACY_TOOLS=1
26
+ ```
27
+
28
+ | Deprecated alias | Use instead |
13
29
  |---|---|
14
- | `hydradb_query` | `hydra_db_search` |
15
- | `hydradb_ingest` | `hydra_db_store`, `hydra_db_ingest_conversation` |
16
- | `hydradb_list` | `hydra_db_list_memories`, `hydra_db_list_sources` |
17
- | `hydradb_inspect` | `hydra_db_fetch_content` |
18
- | `hydradb_delete` | `hydra_db_delete_memory` |
30
+ | `hydra_db_search` | `hydradb_query` |
31
+ | `hydra_db_store`, `hydra_db_ingest_conversation` | `hydradb_ingest` |
32
+ | `hydra_db_list_memories`, `hydra_db_list_sources` | `hydradb_list` |
33
+ | `hydra_db_fetch_content` | `hydradb_inspect` |
34
+ | `hydra_db_delete_memory` | `hydradb_delete` |
19
35
 
20
36
  ### **hydradb_query**
21
37
 
22
- Search through Hydra DB memories. Returns relevant chunks with graph-enriched context including entity paths and knowledge graph relations.
38
+ Searches **both** memories and ingested knowledge sources. Returns matching
39
+ chunks with their source id, a relevance score, and knowledge-graph context.
23
40
 
24
41
  | Parameter | Type | Required | Description |
25
42
  |-----------|------|----------|-------------|
26
- | `query` | string | Yes | The search query to find relevant memories |
27
- | `max_results` | number | No | Maximum number of memory chunks to return (1-50, default: 10) |
28
- | `mode` | string | No | Recall mode: `fast` for quick semantic search, `thinking` for deeper personalised recall with graph traversal (default: `thinking`) |
29
- | `graph_context` | boolean | No | Whether to include knowledge graph relations in results (default: true) |
43
+ | `query` | string | Yes | What you want to know, as a question or topic |
44
+ | `kind` | string | No | `memory`, `knowledge`, or `all` (default: `all`) |
45
+ | `max_results` | number | No | Maximum chunks to return (1-50, default: 10) |
46
+ | `mode` | string | No | `fast`, `thinking` (default), or `auto` |
47
+ | `detail` | string | No | `compact` (default) trims each chunk; `full` returns them whole |
48
+ | `graph_context` | boolean | No | Include knowledge-graph relations (default: true) |
49
+ | `operator` | string | No | `or` (default), `and`, or `phrase` for exact strings |
50
+ | `source_ids` | array | No | Restrict the search to these sources |
51
+ | `metadata_filters` | object | No | Exact-match filters over stored metadata |
52
+ | `num_related_chunks` | number | No | Adjacent chunks to attach per match (0-5, default: 0) |
30
53
 
31
54
  ### **hydradb_ingest**
32
55
 
33
- Save information to Hydra DB memory. Hydra DB automatically extracts insights, preferences, and builds a knowledge graph from the stored content. Provide `text` to store a note/document, or `turns` to ingest a conversation.
56
+ Saves information so it outlives the session. Provide **exactly one** of `text`
57
+ or `turns`.
34
58
 
35
59
  | Parameter | Type | Required | Description |
36
60
  |-----------|------|----------|-------------|
37
- | `text` | string | No\* | The information to store in memory |
38
- | `title` | string | No | Title for the memory entry (default: `MCP Memory`) |
39
- | `source_id` | string | No | Source identifier to group related memories together (e.g. session ID) |
40
- | `infer` | boolean | No | Whether Hydra DB should extract insights and build knowledge graph (default: true) |
41
- | `is_markdown` | boolean | No | Whether the text is in markdown format (default: false) |
42
- | `turns` | array | No\* | Conversation turns (each with a `user` and `assistant` field) to ingest instead of `text` |
43
- | `user_name` | string | No | Name of the user for personalisation, used with `turns` (default: `User`) |
44
-
45
- \* Provide exactly one of `text` or `turns`.
61
+ | `text` | string | No\* | A note, fact, decision, or document body |
62
+ | `turns` | array | No\* | Conversation turns, each with `user` and `assistant` |
63
+ | `kind` | string | No | `memory` (default) or `knowledge` for a document |
64
+ | `title` | string | No | Label shown in later search results always set it |
65
+ | `source_id` | string | No | Identifier for this entry. **Reusing one REPLACES what is stored under it** |
66
+ | `overwrite` | boolean | No | Allow that replacement (default: true) |
67
+ | `infer` | boolean | No | Extract insights and graph entities (default: true) |
68
+ | `is_markdown` | boolean | No | Chunk on markdown structure (default: false) |
69
+ | `metadata` | object | No | Key/value metadata, matchable later via `metadata_filters` |
70
+ | `observation_date` | string | No | When the fact was true (RFC3339), vs when it was stored |
71
+ | `user_name` | string | No | What to call the user, used with `turns` (default: `User`) |
72
+
73
+ \* Passing both is an error; passing neither is an error.
74
+
75
+ Ingestion is **asynchronous** — content is not searchable the instant it is
76
+ saved. Use `hydradb_status` to confirm.
46
77
 
47
78
  ### **hydradb_list**
48
79
 
49
- List stored memories or ingested knowledge sources in Hydra DB.
80
+ Enumerates one family at a time. These are separate corpora: listing memories
81
+ tells you nothing about which knowledge sources exist.
50
82
 
51
83
  | Parameter | Type | Required | Description |
52
84
  |-----------|------|----------|-------------|
53
- | `kind` | string | No | Which family to list: `memory` or `knowledge` (default: `memory`) |
54
- | `source_ids` | array | No | For `knowledge`, an array of specific source IDs to filter by. If omitted, lists all |
85
+ | `kind` | string | **Yes** | `memory` or `knowledge` |
86
+ | `ids` | array | No | Restrict to these ids |
87
+ | `source_ids` | array | No | Deprecated alias for `ids` |
88
+ | `page` | number | No | Page to return, 1-indexed (default: 1) |
89
+ | `page_size` | number | No | Items per page (1-100) |
90
+
91
+ The response reports how many of the total it showed and how to reach the rest.
55
92
 
56
93
  ### **hydradb_inspect**
57
94
 
58
- Fetch the full content of a specific source by its source ID.
95
+ Fetches one source's full content by id.
59
96
 
60
97
  | Parameter | Type | Required | Description |
61
98
  |-----------|------|----------|-------------|
62
- | `source_id` | string | Yes | The source ID to fetch content for |
63
- | `mode` | string | No | Fetch mode: `content` for text, `url` for presigned URL, `both` for both (default: `content`) |
99
+ | `id` | string | Yes | The source id, from `hydradb_query` or `hydradb_list` |
100
+ | `source_id` | string | No | Deprecated alias for `id` |
101
+ | `mode` | string | No | `content` (default), `url` for a download link, or `both` |
102
+ | `offset` | number | No | Character offset to read from (default: 0) |
103
+ | `limit` | number | No | Maximum characters to return (max 20000) |
104
+ | `expiry_seconds` | number | No | How long a `url` link stays valid |
105
+
106
+ Long sources come back in slices, and binary sources are never inlined — you get
107
+ their type and size, and `mode: "url"` returns a download link.
64
108
 
65
109
  ### **hydradb_delete**
66
110
 
67
- Delete a memory or knowledge source from Hydra DB by its ID. This action is irreversible.
111
+ Removes items by id. Irreversible.
112
+
113
+ | Parameter | Type | Required | Description |
114
+ |-----------|------|----------|-------------|
115
+ | `ids` | array | No\* | The ids to delete — accepts several at once |
116
+ | `id` | string | No\* | A single id |
117
+ | `kind` | string | No | `memory` (default) or `knowledge` |
118
+
119
+ \* Provide one of them.
120
+
121
+ ### **hydradb_status**
122
+
123
+ Checks whether ingested sources have finished indexing.
68
124
 
69
125
  | Parameter | Type | Required | Description |
70
126
  |-----------|------|----------|-------------|
71
- | `id` | string | Yes | The ID of the item to delete |
72
- | `kind` | string | No | Which family the ID belongs to: `memory` or `knowledge` (default: `memory`) |
127
+ | `ids` | array | Yes | The source ids to check |
73
128
 
74
129
  ## Configuration
75
130
 
@@ -87,6 +142,9 @@ Delete a memory or knowledge source from Hydra DB by its ID. This action is irre
87
142
  | `HYDRADB_COLLECTION` | Collection (sub-tenant) for partitioning | `hydra-db-mcp` |
88
143
  | `HYDRADB_BASE_URL` | Base URL override | `https://api.hydradb.com` |
89
144
  | `HYDRADB_LOG_LEVEL` | Log level: DEBUG, INFO, WARN, ERROR | `ERROR` |
145
+ | `HYDRADB_TIMEOUT_SECONDS` | Per-attempt request timeout | `30` |
146
+ | `HYDRADB_MAX_RETRIES` | Retries per request (0 disables) | `2` |
147
+ | `HYDRADB_MCP_LEGACY_TOOLS` | Register the deprecated `hydra_db_*` tools | *off* |
90
148
 
91
149
  The legacy `HYDRA_DB_*` names — `HYDRA_DB_API_KEY`, `HYDRA_DB_TENANT_ID`,
92
150
  `HYDRA_DB_SUB_TENANT_ID`, `HYDRA_DB_BASE_URL`, `HYDRA_DB_LOG_LEVEL` — remain
@@ -1,29 +1,39 @@
1
1
  /**
2
- * Adapters from the SDK's (camelCase) response payloads back into the legacy
3
- * snake_case shapes this server already renders.
2
+ * Adapters over responses the SDK does not fully type.
4
3
  *
5
- * These live in the MCP layer NOT the portable wrapper — because they exist
6
- * only to keep `src/context.ts` (the high-value, byte-identical recall
7
- * renderer) and the existing tool output strings unchanged across the v1 SDK
8
- * migration. The wrapper returns SDK-native `.data`; the server adapts it here.
9
- *
10
- * Note: with `skipValidation`, the SDK transforms known fields to camelCase but
11
- * leaves the untyped triplet innards (`relation`/`source`/`target`) as raw
12
- * snake_case — which is exactly what `formatTriplet` reads — so triplets pass
13
- * through untouched.
4
+ * The recall adapters that used to live here are gone: `src/context.ts` now
5
+ * reads the SDK payload directly, so the snake_case mirror they translated into
6
+ * no longer exists. What remains is the listing layer, which is a different
7
+ * thing entirely see `toMemoryList` plus the ingest result, which is read
8
+ * for its per-item errors.
14
9
  */
15
10
  import type { HydraDB as SDK } from "@hydradb/sdk";
16
- import type { AddMemoryResponse, RecallResponse } from "./types.js";
17
- /** SDK retrieval result → the legacy `RecallResponse` fed to `buildRecalledContext`. */
18
- export declare function toRecallResponse(data: SDK.SearchV2RetrievalResult): RecallResponse;
19
- /** SDK ingest result → the legacy `AddMemoryResponse` (success/failed counts). */
11
+ import type { AddMemoryResponse } from "./types.js";
20
12
  export declare function toAddMemoryResponse(data: SDK.IngestionV2SourceUploadResponse): AddMemoryResponse;
21
13
  export interface MemoryListItem {
22
14
  memory_id: string;
23
15
  memory_content: string;
24
16
  }
17
+ /**
18
+ * How much of the corpus a listing actually covered.
19
+ *
20
+ * The server returns this alongside every listing and both adapters used to
21
+ * discard it, which is what let one page be presented as the whole store.
22
+ */
23
+ export interface PageInfo {
24
+ /** Total rows across all pages, when the server reported one. */
25
+ total?: number;
26
+ page?: number;
27
+ page_size?: number;
28
+ total_pages?: number;
29
+ has_next?: boolean;
30
+ }
31
+ export interface MemoryList {
32
+ memories: MemoryListItem[];
33
+ page: PageInfo;
34
+ }
25
35
  /** SDK list result → memory rows. Field names vary across v2 records, so read defensively. */
26
- export declare function toMemoryList(data: SDK.ListV2SourceListResponse): MemoryListItem[];
36
+ export declare function toMemoryList(data: SDK.ListV2SourceListResponse): MemoryList;
27
37
  export interface SourceListItem {
28
38
  id: string;
29
39
  title?: string;
@@ -32,6 +42,7 @@ export interface SourceListItem {
32
42
  export interface SourceList {
33
43
  sources: SourceListItem[];
34
44
  total: number;
45
+ page: PageInfo;
35
46
  }
36
47
  /** SDK list result → knowledge source rows + total. */
37
48
  export declare function toSourceList(data: SDK.ListV2SourceListResponse): SourceList;
package/dist/adapters.js CHANGED
@@ -1,67 +1,39 @@
1
1
  /**
2
- * Adapters from the SDK's (camelCase) response payloads back into the legacy
3
- * snake_case shapes this server already renders.
2
+ * Adapters over responses the SDK does not fully type.
4
3
  *
5
- * These live in the MCP layer NOT the portable wrapper — because they exist
6
- * only to keep `src/context.ts` (the high-value, byte-identical recall
7
- * renderer) and the existing tool output strings unchanged across the v1 SDK
8
- * migration. The wrapper returns SDK-native `.data`; the server adapts it here.
4
+ * The recall adapters that used to live here are gone: `src/context.ts` now
5
+ * reads the SDK payload directly, so the snake_case mirror they translated into
6
+ * no longer exists. What remains is the listing layer, which is a different
7
+ * thing entirely see `toMemoryList` plus the ingest result, which is read
8
+ * for its per-item errors.
9
+ */
10
+ /**
11
+ * SDK ingest result item → `MemoryResultItem`.
9
12
  *
10
- * Note: with `skipValidation`, the SDK transforms known fields to camelCase but
11
- * leaves the untyped triplet innards (`relation`/`source`/`target`) as raw
12
- * snake_case which is exactly what `formatTriplet` reads so triplets pass
13
- * through untouched.
13
+ * `results` used to be hardcoded empty in `toAddMemoryResponse`, which discarded
14
+ * every per-item `status`/`error`/`errorCode`/`relationsError` the server sent.
15
+ * The caller was left with bare counts"1 success, 2 failed" with no reason,
16
+ * no code and no way to tell WHICH item failed — so its only rational recovery
17
+ * was to re-ingest everything, which (a reused `source_id` replaces) can destroy
18
+ * the item that succeeded.
14
19
  */
15
- function toScoredPath(path) {
16
- return {
17
- // Triplet innards are already raw snake_case (untyped in the SDK schema).
18
- triplets: (path.triplets ?? []),
19
- relevancy_score: path.relevancyScore ?? 0,
20
- combined_context: path.combinedContext ?? null,
21
- group_id: path.groupId ?? null,
22
- };
23
- }
24
- function toVectorChunk(chunk) {
20
+ function toMemoryResultItem(item) {
25
21
  return {
26
- chunk_uuid: chunk.chunkUuid ?? "",
27
- source_id: chunk.id ?? "",
28
- chunk_content: chunk.chunkContent ?? "",
29
- source_title: chunk.sourceTitle,
30
- source_type: chunk.sourceType,
31
- source_upload_time: chunk.sourceUploadTime,
32
- source_last_updated_time: chunk.sourceLastUpdatedTime,
33
- relevancy_score: chunk.relevancyScore ?? null,
34
- document_metadata: chunk.additionalMetadata ?? null,
35
- tenant_metadata: chunk.metadata ?? null,
36
- extra_context_ids: chunk.extraContextIds ?? null,
37
- layout: chunk.layout ?? null,
22
+ source_id: item.id ?? "",
23
+ title: item.filename ?? null,
24
+ status: item.status ?? "unknown",
25
+ // The server sends "" rather than omitting these on success; normalise to
26
+ // null so callers can test presence instead of emptiness.
27
+ error: item.error || null,
28
+ error_code: item.errorCode || null,
29
+ relations_error: item.relationsError || null,
38
30
  };
39
31
  }
40
- /** SDK retrieval result → the legacy `RecallResponse` fed to `buildRecalledContext`. */
41
- export function toRecallResponse(data) {
42
- const graph = data.graphContext;
43
- const additional = {};
44
- for (const [id, chunk] of Object.entries(data.additionalContext ?? {})) {
45
- additional[id] = toVectorChunk(chunk);
46
- }
47
- return {
48
- chunks: (data.chunks ?? []).map(toVectorChunk),
49
- graph_context: graph
50
- ? {
51
- query_paths: (graph.queryPaths ?? []).map(toScoredPath),
52
- chunk_relations: (graph.chunkRelations ?? []).map(toScoredPath),
53
- chunk_id_to_group_ids: graph.chunkIdToGroupIds ?? {},
54
- }
55
- : undefined,
56
- additional_context: additional,
57
- };
58
- }
59
- /** SDK ingest result → the legacy `AddMemoryResponse` (success/failed counts). */
60
32
  export function toAddMemoryResponse(data) {
61
33
  return {
62
34
  success: data.success ?? false,
63
35
  message: data.message ?? "",
64
- results: [],
36
+ results: (data.results ?? []).map(toMemoryResultItem),
65
37
  success_count: data.successCount ?? 0,
66
38
  failed_count: data.failedCount ?? 0,
67
39
  };
@@ -79,23 +51,54 @@ function asRecords(value) {
79
51
  ? value
80
52
  : undefined;
81
53
  }
54
+ function num(record, ...keys) {
55
+ for (const key of keys) {
56
+ const value = record[key];
57
+ if (typeof value === "number")
58
+ return value;
59
+ }
60
+ return undefined;
61
+ }
62
+ /**
63
+ * Pagination metadata, read defensively for the same reason the rows are: the
64
+ * SDK types this response as `{ inner?: … }` while the live API returns at top
65
+ * level, and neither shape is guaranteed to carry every field.
66
+ */
67
+ function toPageInfo(container, rowCount) {
68
+ const meta = container.pagination ?? {};
69
+ const total = num(container, "total") ?? num(meta, "total");
70
+ return {
71
+ total: total ?? rowCount,
72
+ page: num(meta, "page"),
73
+ page_size: num(meta, "page_size", "pageSize"),
74
+ total_pages: num(meta, "total_pages", "totalPages"),
75
+ has_next: typeof meta.has_next === "boolean"
76
+ ? meta.has_next
77
+ : typeof meta.hasNext === "boolean"
78
+ ? meta.hasNext
79
+ : undefined,
80
+ };
81
+ }
82
82
  /** SDK list result → memory rows. Field names vary across v2 records, so read defensively. */
83
83
  export function toMemoryList(data) {
84
84
  // Memory listings surface at top-level `user_memories` — not under an
85
85
  // `.inner` wrapper, and not under `sources` (that is the knowledge shape).
86
86
  const d = data;
87
+ const container = (asRecords(d.user_memories) ? d : d.inner) ?? d;
87
88
  const records = asRecords(d.user_memories) ??
88
89
  asRecords(d.inner?.user_memories) ??
89
90
  [];
90
- return records.map((record) => ({
91
+ const memories = records.map((record) => ({
91
92
  memory_id: str(record, "memory_id", "id", "source_id") ?? "",
92
93
  memory_content: str(record, "memory_content", "content", "text", "memory", "title") ?? "",
93
94
  }));
95
+ return { memories, page: toPageInfo(container, memories.length) };
94
96
  }
95
97
  /** SDK list result → knowledge source rows + total. */
96
98
  export function toSourceList(data) {
97
99
  // Knowledge listings surface at top-level `sources`, not under `.inner`.
98
100
  const d = data;
101
+ const container = (asRecords(d.sources) ? d : d.inner) ?? d;
99
102
  const records = asRecords(d.sources) ??
100
103
  asRecords(d.inner?.sources) ??
101
104
  [];
@@ -108,6 +111,7 @@ export function toSourceList(data) {
108
111
  return {
109
112
  sources,
110
113
  total: typeof total === "number" ? total : sources.length,
114
+ page: toPageInfo(container, sources.length),
111
115
  };
112
116
  }
113
117
  //# sourceMappingURL=adapters.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"adapters.js","sourceRoot":"","sources":["../src/adapters.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"adapters.js","sourceRoot":"","sources":["../src/adapters.ts"],"names":[],"mappings":"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"}
package/dist/config.d.ts CHANGED
@@ -12,6 +12,8 @@ export interface HydraDBConfig {
12
12
  database: string;
13
13
  collection: string;
14
14
  baseUrl?: string;
15
+ timeoutSeconds?: number;
16
+ maxRetries?: number;
15
17
  }
16
18
  export type EnvSource = Record<string, string | undefined>;
17
19
  export type WarnFn = (message: string) => void;
package/dist/config.js CHANGED
@@ -50,6 +50,32 @@ export function resolveConfig(env = process.env, warn = defaultWarn) {
50
50
  const collection = readEnv(env, "HYDRADB_COLLECTION", "HYDRA_DB_SUB_TENANT_ID", warn) ??
51
51
  DEFAULT_COLLECTION;
52
52
  const baseUrl = readEnv(env, "HYDRADB_BASE_URL", "HYDRA_DB_BASE_URL", warn);
53
- return { apiKey, database, collection, baseUrl };
53
+ // Included only when actually set, so the resolved config says what the
54
+ // environment said rather than carrying a row of undefined knobs.
55
+ const timeoutSeconds = positiveInt(env.HYDRADB_TIMEOUT_SECONDS);
56
+ const maxRetries = nonNegativeInt(env.HYDRADB_MAX_RETRIES);
57
+ return {
58
+ apiKey,
59
+ database,
60
+ collection,
61
+ baseUrl,
62
+ ...(timeoutSeconds != null ? { timeoutSeconds } : {}),
63
+ ...(maxRetries != null ? { maxRetries } : {}),
64
+ };
65
+ }
66
+ /**
67
+ * Numeric overrides are ignored rather than fatal when malformed.
68
+ *
69
+ * A typo'd timeout should not stop the server from starting — falling back to
70
+ * the built-in default keeps it running, and the alternative (exit 1 on a
71
+ * cosmetic env var) is worse than the misconfiguration.
72
+ */
73
+ function positiveInt(raw) {
74
+ const value = Number(raw);
75
+ return raw != null && Number.isInteger(value) && value > 0 ? value : undefined;
76
+ }
77
+ function nonNegativeInt(raw) {
78
+ const value = Number(raw);
79
+ return raw != null && Number.isInteger(value) && value >= 0 ? value : undefined;
54
80
  }
55
81
  //# sourceMappingURL=config.js.map