@mindstudio-ai/agent 0.1.86 → 0.1.87

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/dist/index.d.ts CHANGED
@@ -1508,6 +1508,16 @@ interface Citation {
1508
1508
  filename: string | null;
1509
1509
  /** 1-based. Null for formats with no pagination (plain text, html). */
1510
1510
  pageNumber: number | null;
1511
+ /**
1512
+ * Position within the document, 0-based.
1513
+ *
1514
+ * `(documentId, chunkIndex)` is the stable identity of a chunk — use it as
1515
+ * the key for an eval set or a regression check rather than matching on
1516
+ * `text`. Stable for as long as the corpus keeps its current configuration:
1517
+ * re-chunking a source (a different chunk size, say) moves the boundaries and
1518
+ * therefore renumbers, which is inherent rather than a wobble.
1519
+ */
1520
+ chunkIndex: number | null;
1511
1521
  /** Enclosing headings, outermost first. */
1512
1522
  headingPath: string[];
1513
1523
  /**
@@ -1527,12 +1537,49 @@ interface Citation {
1527
1537
  */
1528
1538
  url: string;
1529
1539
  }
1540
+ /** Where one retrieval branch put a hit, and what that branch scored it. */
1541
+ interface BranchPosition {
1542
+ /** 0-based position within that branch's own results. */
1543
+ rank: number;
1544
+ score: number;
1545
+ }
1546
+ /**
1547
+ * Which half of hybrid retrieval found a hit. Only present when `explain` was
1548
+ * requested — see {@link SearchOptions.explain}.
1549
+ */
1550
+ interface SearchExplain {
1551
+ /** Semantic (embedding) retrieval. Null if this branch didn't find the hit. */
1552
+ dense: BranchPosition | null;
1553
+ /** Keyword/IDF retrieval. Null when `hybrid` is off, or if it didn't find it. */
1554
+ lexical: BranchPosition | null;
1555
+ matchedVia: 'dense' | 'lexical' | 'both';
1556
+ }
1530
1557
  interface SearchHit {
1531
1558
  /** Provider relevance score. Comparable within a response, not across them. */
1532
1559
  score: number;
1533
1560
  /** The matched chunk, prefixed with its heading path for context. */
1534
1561
  text: string;
1535
1562
  citation: Citation;
1563
+ /**
1564
+ * Where retrieval put this hit BEFORE reranking, and what it scored.
1565
+ *
1566
+ * With reranking on, `score` is the reranker's relevance score and this is
1567
+ * the retriever's — different quantities, so they're kept apart rather than
1568
+ * blended. Comparing `retrievalRank` with the hit's final position is how you
1569
+ * see what reranking actually did ("retrieved 7th, reranked to 1st").
1570
+ *
1571
+ * Named for the stage rather than the method: it's a fused hybrid score when
1572
+ * `hybrid` is on and a cosine similarity when it's off.
1573
+ */
1574
+ retrievalRank?: number;
1575
+ retrievalScore?: number;
1576
+ /** Only when `explain` was requested. */
1577
+ explain?: SearchExplain;
1578
+ /** Only when `expand` was requested. Outermost first, so `[...before, text, ...after]` reads in order. */
1579
+ neighbors?: {
1580
+ before: string[];
1581
+ after: string[];
1582
+ };
1536
1583
  }
1537
1584
  /**
1538
1585
  * Per-query overrides.
@@ -1566,6 +1613,23 @@ interface SearchOptions {
1566
1613
  * that an embedding model never learned. Rarely worth disabling.
1567
1614
  */
1568
1615
  hybrid?: boolean;
1616
+ /**
1617
+ * Report which branch found each hit, and where each ranked it.
1618
+ *
1619
+ * A debugging aid, off by default because it costs two extra round trips: a
1620
+ * fused result carries one blended score, so the branches have to be asked
1621
+ * separately. Results and their order are identical either way — this only
1622
+ * adds {@link SearchHit.explain}.
1623
+ */
1624
+ explain?: boolean;
1625
+ /**
1626
+ * Also return this many chunks either side of each hit, in
1627
+ * {@link SearchHit.neighbors} — for showing a passage in context. 0-2.
1628
+ *
1629
+ * `text` is untouched, so citations and highlighting still point at the
1630
+ * chunk that actually matched.
1631
+ */
1632
+ expand?: number;
1569
1633
  }
1570
1634
  interface AddOptions {
1571
1635
  /**
@@ -1585,6 +1649,68 @@ interface DataSourceDocument {
1585
1649
  createdAt: string;
1586
1650
  ingestedAt: string | null;
1587
1651
  }
1652
+ /** One chunk exactly as it was indexed. See {@link DataSource.chunks}. */
1653
+ interface DataSourceChunk {
1654
+ index: number;
1655
+ text: string;
1656
+ pageNumber: number;
1657
+ headingPath: string[];
1658
+ /**
1659
+ * Offsets into the page's extracted markdown. Null for PDFs, which carry a
1660
+ * `boundingBox` instead — there is no character stream to point into.
1661
+ */
1662
+ charStart: number | null;
1663
+ charEnd: number | null;
1664
+ boundingBox?: {
1665
+ topLeftX: number;
1666
+ topLeftY: number;
1667
+ bottomRightX: number;
1668
+ bottomRightY: number;
1669
+ };
1670
+ /** base64 of a Float32Array. Only when `vectors: true` was passed. */
1671
+ vector?: string;
1672
+ }
1673
+ /** How a corpus was built and what is in it. See {@link DataSource.stats}. */
1674
+ interface DataSourceStats {
1675
+ /** False for a source nothing has created yet — everything else reads zero. */
1676
+ exists: boolean;
1677
+ documentCount: number;
1678
+ counts: {
1679
+ total: number;
1680
+ done: number;
1681
+ processing: number;
1682
+ error: number;
1683
+ };
1684
+ chunkCount: number;
1685
+ /** Original document bytes, not index size. */
1686
+ storageBytes: number;
1687
+ lastIngestedAt: string | null;
1688
+ /**
1689
+ * The configuration these documents were actually built with — not the
1690
+ * platform default, and not necessarily the newest. Changing it is an
1691
+ * explicit, owner-triggered migration.
1692
+ */
1693
+ pipeline: {
1694
+ version: number;
1695
+ embeddingModelId: string;
1696
+ dimensions: number;
1697
+ chunking: {
1698
+ strategy: string;
1699
+ version: number;
1700
+ maxChars: number;
1701
+ minChars: number;
1702
+ dropBlockTypes: string[];
1703
+ };
1704
+ contextual: {
1705
+ enabled: boolean;
1706
+ modelId: string | null;
1707
+ };
1708
+ images: {
1709
+ describe: boolean;
1710
+ modelId: string | null;
1711
+ };
1712
+ } | null;
1713
+ }
1588
1714
  /**
1589
1715
  * A typed handle to one data source. Lazy — nothing executes until a method is
1590
1716
  * awaited, so it's safe to `defineDataSource()` at module scope and import the
@@ -1602,10 +1728,39 @@ declare class DataSource {
1602
1728
  * Returns chunks ranked by relevance, each with a citation. Searching a
1603
1729
  * source that doesn't exist yet returns no results rather than throwing —
1604
1730
  * code may name a corpus the build hasn't populated.
1731
+ *
1732
+ * **Deterministic** for a fixed corpus and configuration: the same query
1733
+ * returns the same hits in the same order, so it's safe to build an eval set
1734
+ * or a regression check on top of it. There is no seed to set. Two things do
1735
+ * legitimately move the results: adding or removing documents, and changing
1736
+ * the corpus configuration — both of which you control.
1605
1737
  */
1606
1738
  search(query: string, options?: SearchOptions): Promise<{
1607
1739
  results: SearchHit[];
1740
+ latencyMs: number;
1608
1741
  }>;
1742
+ /**
1743
+ * What is in the corpus, and how it was built.
1744
+ *
1745
+ * Document and chunk counts, storage, and the embedding model and chunking
1746
+ * settings actually in effect — which is not the same as the platform
1747
+ * default, since a corpus keeps the configuration it was built with until
1748
+ * someone migrates it.
1749
+ */
1750
+ stats(): Promise<DataSourceStats>;
1751
+ /**
1752
+ * Every chunk of one document, exactly as it was indexed.
1753
+ *
1754
+ * The direct answer to "why isn't this document coming back?" — search only
1755
+ * shows you the chunks that surface, which is no help when none do. Reading
1756
+ * how a document was actually split usually is.
1757
+ *
1758
+ * Pass `{ vectors: true }` to include each chunk's embedding. Large: roughly
1759
+ * 8KB per chunk, so a 500-chunk document is several megabytes.
1760
+ */
1761
+ chunks(documentId: string, options?: {
1762
+ vectors?: boolean;
1763
+ }): Promise<DataSourceChunk[]>;
1609
1764
  /**
1610
1765
  * Add a document to the corpus.
1611
1766
  *
@@ -2300,6 +2455,12 @@ declare class MindStudioAgent$1 {
2300
2455
  *
2301
2456
  * Gets a presigned upload request from the API, POSTs the file as
2302
2457
  * multipart/form-data, and returns the permanent public URL.
2458
+ *
2459
+ * @deprecated For app file storage use the `files` store
2460
+ * (`files.defineStore(...).put(...)`) — private by default, app-scoped, and
2461
+ * served on the app's own domain. `uploadFile` uploads to the shared account
2462
+ * media CDN; it remains only for account-level assets (e.g. an agent avatar
2463
+ * passed to `changeProfilePicture`).
2303
2464
  */
2304
2465
  uploadFile(content: Buffer | Uint8Array, options: {
2305
2466
  extension: string;
package/dist/index.js CHANGED
@@ -2128,17 +2128,54 @@ var DataSource = class {
2128
2128
  * Returns chunks ranked by relevance, each with a citation. Searching a
2129
2129
  * source that doesn't exist yet returns no results rather than throwing —
2130
2130
  * code may name a corpus the build hasn't populated.
2131
+ *
2132
+ * **Deterministic** for a fixed corpus and configuration: the same query
2133
+ * returns the same hits in the same order, so it's safe to build an eval set
2134
+ * or a regression check on top of it. There is no seed to set. Two things do
2135
+ * legitimately move the results: adding or removing documents, and changing
2136
+ * the corpus configuration — both of which you control.
2131
2137
  */
2132
2138
  async search(query, options) {
2133
- const { results } = await this._call("search", {
2139
+ const { results, latencyMs } = await this._call("search", {
2134
2140
  slug: this._slug,
2135
2141
  query,
2136
2142
  ...options?.topK !== void 0 ? { topK: options.topK } : {},
2137
2143
  ...options?.scoreThreshold !== void 0 ? { scoreThreshold: options.scoreThreshold } : {},
2138
2144
  ...options?.rerank !== void 0 ? { rerank: options.rerank } : {},
2139
- ...options?.hybrid !== void 0 ? { hybrid: options.hybrid } : {}
2145
+ ...options?.hybrid !== void 0 ? { hybrid: options.hybrid } : {},
2146
+ ...options?.explain !== void 0 ? { explain: options.explain } : {},
2147
+ ...options?.expand !== void 0 ? { expand: options.expand } : {}
2140
2148
  });
2141
- return { results: results ?? [] };
2149
+ return { results: results ?? [], latencyMs: latencyMs ?? 0 };
2150
+ }
2151
+ /**
2152
+ * What is in the corpus, and how it was built.
2153
+ *
2154
+ * Document and chunk counts, storage, and the embedding model and chunking
2155
+ * settings actually in effect — which is not the same as the platform
2156
+ * default, since a corpus keeps the configuration it was built with until
2157
+ * someone migrates it.
2158
+ */
2159
+ async stats() {
2160
+ return this._call("stats", { slug: this._slug });
2161
+ }
2162
+ /**
2163
+ * Every chunk of one document, exactly as it was indexed.
2164
+ *
2165
+ * The direct answer to "why isn't this document coming back?" — search only
2166
+ * shows you the chunks that surface, which is no help when none do. Reading
2167
+ * how a document was actually split usually is.
2168
+ *
2169
+ * Pass `{ vectors: true }` to include each chunk's embedding. Large: roughly
2170
+ * 8KB per chunk, so a 500-chunk document is several megabytes.
2171
+ */
2172
+ async chunks(documentId, options) {
2173
+ const { chunks } = await this._call("chunks", {
2174
+ slug: this._slug,
2175
+ documentId,
2176
+ ...options?.vectors ? { vectors: true } : {}
2177
+ });
2178
+ return chunks ?? [];
2142
2179
  }
2143
2180
  /**
2144
2181
  * Add a document to the corpus.
@@ -5572,6 +5609,12 @@ var MindStudioAgent = class {
5572
5609
  *
5573
5610
  * Gets a presigned upload request from the API, POSTs the file as
5574
5611
  * multipart/form-data, and returns the permanent public URL.
5612
+ *
5613
+ * @deprecated For app file storage use the `files` store
5614
+ * (`files.defineStore(...).put(...)`) — private by default, app-scoped, and
5615
+ * served on the app's own domain. `uploadFile` uploads to the shared account
5616
+ * media CDN; it remains only for account-level assets (e.g. an agent avatar
5617
+ * passed to `changeProfilePicture`).
5575
5618
  */
5576
5619
  async uploadFile(content, options) {
5577
5620
  const filename = options.filename ?? `upload.${options.extension}`;