@mindstudio-ai/agent 0.1.118 → 0.1.119

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.js CHANGED
@@ -2840,6 +2840,7 @@ function createFiles(call) {
2840
2840
 
2841
2841
  // src/datasources/source.ts
2842
2842
  import { createHash as createHash2 } from "crypto";
2843
+ var REMOVE_WHERE_MAX_PAGES = 100;
2843
2844
  var DataSource = class {
2844
2845
  constructor(_slug, _call) {
2845
2846
  this._slug = _slug;
@@ -2864,6 +2865,15 @@ var DataSource = class {
2864
2865
  * Alongside `results` and `latencyMs` comes {@link SearchRan} — what the
2865
2866
  * search actually did, which is the first thing to check when results don't
2866
2867
  * look like the options you passed.
2868
+ *
2869
+ * **Throws `index_warming`** (HTTP 503) when a large corpus's index is being
2870
+ * reloaded after an eviction: the platform keeps a working set of indexes
2871
+ * resident and reloads the rest on first use, which for a big source takes a
2872
+ * minute or two in the background. Treat it as "loading", never as "empty" —
2873
+ * tell the user the knowledge base is warming up and retry shortly. Small
2874
+ * corpora reload inside the search and never raise it. Sources on dedicated
2875
+ * capacity are never evicted, so they never raise it either; they raise
2876
+ * `capacity_hibernated` and friends instead when their capacity is parked.
2867
2877
  */
2868
2878
  async search(query, options) {
2869
2879
  const { results, mode, latencyMs } = await this._call("search", {
@@ -2878,10 +2888,29 @@ var DataSource = class {
2878
2888
  ...options?.rerank !== void 0 ? { rerank: options.rerank } : {},
2879
2889
  ...options?.hybrid !== void 0 ? { hybrid: options.hybrid } : {},
2880
2890
  ...options?.explain !== void 0 ? { explain: options.explain } : {},
2881
- ...options?.expand !== void 0 ? { expand: options.expand } : {}
2891
+ ...options?.expand !== void 0 ? { expand: options.expand } : {},
2892
+ ...options?.shareCitations !== void 0 ? { shareCitations: options.shareCitations } : {}
2882
2893
  });
2883
2894
  return { results: results ?? [], mode, latencyMs: latencyMs ?? 0 };
2884
2895
  }
2896
+ /**
2897
+ * Mint an ABSOLUTE, signed link to one document that works with **no**
2898
+ * session — the same verb as `store.shareUrl()` for a file. Takes a
2899
+ * document id or anything carrying one (`hit.citation`). Expires (default
2900
+ * 24h; clamped to 60s–90d); each click still redirects to a fresh
2901
+ * short-lived storage URL, so the expiry you pass is the only one that
2902
+ * matters. For every citation of a search at once, pass
2903
+ * {@link SearchOptions.shareCitations} instead.
2904
+ */
2905
+ async shareUrl(document, options) {
2906
+ const documentId = typeof document === "string" ? document : document.documentId;
2907
+ const res = await this._call("share", {
2908
+ slug: this._slug,
2909
+ documentId,
2910
+ ...options?.expiresIn ? { expiresIn: options.expiresIn } : {}
2911
+ });
2912
+ return res.url;
2913
+ }
2885
2914
  /**
2886
2915
  * What is in the corpus, and how it was built.
2887
2916
  *
@@ -2923,6 +2952,19 @@ var DataSource = class {
2923
2952
  * bytes under its current configuration. Reconfiguring the source is what
2924
2953
  * makes a re-add do work again — and that is an explicit, owner-triggered
2925
2954
  * migration, never something a deploy causes.
2955
+ *
2956
+ * **On a source with a mapper** (`dataSources[].mapper` in mindstudio.json,
2957
+ * see `defineMapper`) the bytes are a raw object and the mapper decides
2958
+ * what they become: `documents` holds everything it produced (one object
2959
+ * can become many documents), `document` is the first of them for
2960
+ * convenience, and `outcome` says which way the mapper went. A
2961
+ * `passthrough` is one document from the bytes themselves; a `deletes`
2962
+ * outcome removes the named documents and returns none. A document whose
2963
+ * `externalId` (or `replaces`) names an existing one supersedes it as soon
2964
+ * as the new one is registered, before its build lands; a search in that
2965
+ * window sees neither. **Throws `mapper_skipped`** (HTTP 422) when the
2966
+ * mapper skipped the object — a specific thing it refused, which the caller
2967
+ * should see — and `mapper_error` when the mapper threw.
2926
2968
  */
2927
2969
  async add(content, options) {
2928
2970
  const bytes = typeof content === "string" ? Buffer.from(content) : Buffer.from(content);
@@ -2934,15 +2976,104 @@ var DataSource = class {
2934
2976
  body: bytes.toString("base64")
2935
2977
  });
2936
2978
  }
2937
- /** Every document in the corpus, with ingest status. */
2938
- async documents() {
2939
- const { documents } = await this._call("documents", { slug: this._slug });
2940
- return documents ?? [];
2979
+ /**
2980
+ * Documents in the corpus, with ingest status.
2981
+ *
2982
+ * Without options this is the first thousand, newest first: enough to show
2983
+ * a small corpus, not a way to walk a large one (a corpus of any size is
2984
+ * loaded and inspected from the CLI). To follow what {@link add} just
2985
+ * queued, pass the ids it returned: `documents({ ids })` answers only those,
2986
+ * at most two hundred per call.
2987
+ */
2988
+ async documents(options) {
2989
+ const { documents: documents2 } = await this._call("documents", {
2990
+ slug: this._slug,
2991
+ ...options?.ids?.length ? { ids: options.ids } : {}
2992
+ });
2993
+ return documents2 ?? [];
2941
2994
  }
2942
2995
  /** Remove a document and its vectors. */
2943
2996
  async remove(documentId) {
2944
2997
  await this._call("remove", { slug: this._slug, documentId });
2945
2998
  }
2999
+ /**
3000
+ * Remove every document matching a selector — by metadata tags, filename,
3001
+ * ids, or the external id prefix a bulk load or connector recorded.
3002
+ *
3003
+ * Works in pages of a thousand and keeps going until nothing matches (or a
3004
+ * very large removal has run for a hundred pages, in which case `remaining`
3005
+ * says how many are left and a second call continues). Vectors, the catalog
3006
+ * row and the stored bytes all go; the removal is immediate and shared
3007
+ * across dev and prod like every data-source write.
3008
+ *
3009
+ * ```ts
3010
+ * await Policies.removeWhere({ metadata: { department: 'legal', year: 2019 } });
3011
+ * await Policies.removeWhere({ externalIdPrefix: 'archive/2019/' });
3012
+ * ```
3013
+ */
3014
+ async removeWhere(selector) {
3015
+ let deleted = 0;
3016
+ let remaining = 0;
3017
+ for (let page = 0; page < REMOVE_WHERE_MAX_PAGES; page++) {
3018
+ const result = await this._call("remove-many", {
3019
+ slug: this._slug,
3020
+ filter: selector
3021
+ });
3022
+ deleted += result?.deleted ?? 0;
3023
+ remaining = result?.remaining ?? 0;
3024
+ if (remaining === 0 || !(result?.deleted > 0)) {
3025
+ break;
3026
+ }
3027
+ }
3028
+ return { deleted, remaining };
3029
+ }
3030
+ /**
3031
+ * Bring the source up to date with the S3 bucket it is connected to.
3032
+ *
3033
+ * Lists the bucket, diffs it against what was ingested before (by ETag), and
3034
+ * runs an ingestion job over what is new or changed; keys that disappeared
3035
+ * upstream are removed at the end when the connector's deletion policy says
3036
+ * so. Returns as soon as the job is started — it plans, then runs in the
3037
+ * background. Poll {@link job} for progress, or read the latest sync from
3038
+ * {@link jobs}.
3039
+ *
3040
+ * The job auto-approves under the connector's per-sync budget. A plan over
3041
+ * that budget, or over the capacity of where the source lives, waits in
3042
+ * `planned` for a human (`remy-admin datasources jobs approve <id>`) — so the
3043
+ * first backfill of a large bucket stops for approval by itself, and the
3044
+ * steady-state deltas run unattended. Nothing is spent when nothing changed.
3045
+ *
3046
+ * The connection itself — which bucket, which app secrets hold the keys — is
3047
+ * created by the app's owner with `remy-admin datasources connect`, never
3048
+ * from code. A nightly sync is an ordinary cron interface job whose method
3049
+ * calls this:
3050
+ *
3051
+ * ```ts
3052
+ * // methods/sync-archive.ts, scheduled "0 3 * * *" in the cron interface
3053
+ * export default async function () {
3054
+ * await Archive.sync();
3055
+ * }
3056
+ * ```
3057
+ *
3058
+ * @throws `connector_not_found` when the source is not connected;
3059
+ * `data_source_busy` while another job or a move is in flight.
3060
+ */
3061
+ async sync(options) {
3062
+ return this._call("sync", {
3063
+ slug: this._slug,
3064
+ ...options?.budgetDollars !== void 0 ? { budgetDollars: options.budgetDollars } : {}
3065
+ });
3066
+ }
3067
+ /** Recent ingestion jobs on this source (syncs and CLI loads), newest first. */
3068
+ async jobs() {
3069
+ const { jobs } = await this._call("jobs", { slug: this._slug });
3070
+ return jobs ?? [];
3071
+ }
3072
+ /** One job by id — progress, plan, failures, and why it paused if it did. */
3073
+ async job(id) {
3074
+ const { job } = await this._call("job", { slug: this._slug, id });
3075
+ return job;
3076
+ }
2946
3077
  /**
2947
3078
  * Create the data source if it doesn't exist yet.
2948
3079
  *
@@ -7757,6 +7888,136 @@ function defineJewel(method, config) {
7757
7888
  });
7758
7889
  }
7759
7890
 
7891
+ // src/mapper/index.ts
7892
+ function documents(list) {
7893
+ return { kind: "documents", documents: Array.isArray(list) ? list : [list] };
7894
+ }
7895
+ function passthrough(options) {
7896
+ return options?.metadata !== void 0 ? { kind: "passthrough", metadata: options.metadata } : { kind: "passthrough" };
7897
+ }
7898
+ function skip(reason) {
7899
+ return { kind: "skip", reason };
7900
+ }
7901
+ function deletes(externalIds) {
7902
+ return {
7903
+ kind: "deletes",
7904
+ externalIds: Array.isArray(externalIds) ? externalIds : [externalIds]
7905
+ };
7906
+ }
7907
+ var DEFAULT_TIMEOUT_MS = 3e4;
7908
+ var DEFAULT_CONCURRENCY = 4;
7909
+ function errorInfo2(e) {
7910
+ if (e instanceof Error) return { message: e.message, stack: e.stack };
7911
+ return { message: String(e) };
7912
+ }
7913
+ function objectFor(input) {
7914
+ let cached = null;
7915
+ const bytes = () => {
7916
+ if (!cached) {
7917
+ cached = (async () => {
7918
+ const response = await fetch(input.url);
7919
+ if (!response.ok) {
7920
+ throw new Error(
7921
+ `Could not read ${input.key}: HTTP ${response.status}`
7922
+ );
7923
+ }
7924
+ return new Uint8Array(await response.arrayBuffer());
7925
+ })();
7926
+ }
7927
+ return cached;
7928
+ };
7929
+ return {
7930
+ key: input.key,
7931
+ size: input.size,
7932
+ contentType: input.contentType,
7933
+ etag: input.etag,
7934
+ lastModified: input.lastModified,
7935
+ metadata: input.metadata,
7936
+ bytes,
7937
+ text: async () => new TextDecoder("utf-8").decode(await bytes()),
7938
+ json: async () => JSON.parse(new TextDecoder("utf-8").decode(await bytes()))
7939
+ };
7940
+ }
7941
+ var OUTCOME_KINDS = /* @__PURE__ */ new Set(["documents", "passthrough", "skip", "deletes"]);
7942
+ function normalizeOutcome(value) {
7943
+ const outcome = value;
7944
+ if (!outcome || typeof outcome !== "object" || !OUTCOME_KINDS.has(outcome.kind)) {
7945
+ return {
7946
+ kind: "error",
7947
+ error: {
7948
+ message: `map returned an invalid outcome: ${JSON.stringify(value)?.slice(0, 200)}. Return documents(), passthrough(), skip() or deletes().`
7949
+ }
7950
+ };
7951
+ }
7952
+ return outcome;
7953
+ }
7954
+ async function withTimeout(work, timeoutMs, key) {
7955
+ let timer;
7956
+ try {
7957
+ return await Promise.race([
7958
+ work,
7959
+ new Promise((_, reject) => {
7960
+ timer = setTimeout(
7961
+ () => reject(new Error(`map timed out after ${timeoutMs}ms on ${key}`)),
7962
+ timeoutMs
7963
+ );
7964
+ })
7965
+ ]);
7966
+ } finally {
7967
+ if (timer) clearTimeout(timer);
7968
+ }
7969
+ }
7970
+ function defineMapper(source, config) {
7971
+ const run = async (params) => {
7972
+ const startedAt = Date.now();
7973
+ const timeoutMs = params.timeoutMs ?? DEFAULT_TIMEOUT_MS;
7974
+ const concurrency = Math.max(1, params.concurrency ?? DEFAULT_CONCURRENCY);
7975
+ const objects = Array.isArray(params.objects) ? params.objects : [];
7976
+ const outcomes = new Array(objects.length);
7977
+ let next = 0;
7978
+ const worker = async () => {
7979
+ while (true) {
7980
+ const index = next++;
7981
+ if (index >= objects.length) return;
7982
+ const input = objects[index];
7983
+ const t = Date.now();
7984
+ let outcome;
7985
+ try {
7986
+ outcome = normalizeOutcome(
7987
+ await withTimeout(
7988
+ Promise.resolve().then(() => config.map(objectFor(input))),
7989
+ timeoutMs,
7990
+ input.key
7991
+ )
7992
+ );
7993
+ } catch (e) {
7994
+ outcome = { kind: "error", error: errorInfo2(e) };
7995
+ }
7996
+ outcomes[index] = {
7997
+ key: input.key,
7998
+ outcome,
7999
+ durationMs: Date.now() - t
8000
+ };
8001
+ }
8002
+ };
8003
+ await Promise.all(
8004
+ Array.from({ length: Math.min(concurrency, objects.length) }, worker)
8005
+ );
8006
+ return {
8007
+ v: 1,
8008
+ source: source.name,
8009
+ outcomes,
8010
+ startedAt,
8011
+ durationMs: Date.now() - startedAt
8012
+ };
8013
+ };
8014
+ return Object.assign(run, {
8015
+ kind: "mapper",
8016
+ source: source.name,
8017
+ config
8018
+ });
8019
+ }
8020
+
7760
8021
  // src/generated/snippets.ts
7761
8022
  var monacoSnippets = {
7762
8023
  "activeCampaignAddNote": { fields: [["contactId", "string"], ["note", "string"]], outputKeys: [] },
@@ -8031,17 +8292,22 @@ export {
8031
8292
  db,
8032
8293
  index_default as default,
8033
8294
  defineJewel,
8295
+ defineMapper,
8296
+ deletes,
8297
+ documents,
8034
8298
  email,
8035
8299
  events,
8036
8300
  files,
8037
8301
  getRequestContext,
8038
8302
  mindstudio,
8039
8303
  monacoSnippets,
8304
+ passthrough,
8040
8305
  prerender,
8041
8306
  reportIssue,
8042
8307
  resolveUser,
8043
8308
  runWithContext,
8044
8309
  session,
8310
+ skip,
8045
8311
  stepMetadata,
8046
8312
  stream,
8047
8313
  voice,