@mindstudio-ai/agent 0.1.108 → 0.1.110

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
@@ -109,6 +109,13 @@ declare class Store {
109
109
  get access(): FileAccess;
110
110
  /** Store bytes. Returns a {@link StoredFile} with a ready-to-use `url`. */
111
111
  put(content: Buffer | Uint8Array | string, options?: PutOptions): Promise<StoredFile>;
112
+ /**
113
+ * Large-content half of {@link put}: mint a presigned POST scoped to exactly
114
+ * these bytes and send them direct to storage — the JSON transport's body
115
+ * limit never sees them. Same result contract as the small path; the final
116
+ * `head` keeps the returned metadata honest (server-observed size/type/time).
117
+ */
118
+ private _putPresigned;
112
119
  /** Read an object's bytes (backend / trusted context). */
113
120
  get(key: string): Promise<Buffer>;
114
121
  /** Metadata without downloading. Rejects if the object doesn't exist. */
@@ -2352,7 +2359,7 @@ interface SearchRan {
2352
2359
  * vector, so they take effect on the next call and cost nothing. Anything that
2353
2360
  * would require rebuilding the corpus — chunking, the embedding model, whether
2354
2361
  * chunks are contextualized — is a property of the data source, configured
2355
- * with `mindstudio-prod datasources config` rather than passed here.
2362
+ * with `remy-admin datasources config` rather than passed here.
2356
2363
  *
2357
2364
  * The defaults come from the data source's own configuration, so most callers
2358
2365
  * should pass nothing.
@@ -2862,12 +2869,16 @@ interface Analytics {
2862
2869
  }
2863
2870
 
2864
2871
  /**
2865
- * The `email` namespace: read and manage your app's OWN outbound email.
2872
+ * The `email` namespace: send, read and manage your app's OWN outbound email.
2866
2873
  *
2867
- * Sending is not here — that's the `sendEmail` action
2868
- * (`mindstudio.sendEmail({...})`). This namespace is everything *after* the send:
2869
- * what happened to each message, how a campaign performed, and who has
2870
- * unsubscribed.
2874
+ * `email.send()` is the send path. The older `mindstudio.sendEmail({...})` action
2875
+ * still works and shares exactly the same implementation, so nothing breaks — but
2876
+ * `send()` is the one to reach for: it returns a proper receipt (including the
2877
+ * `batchId` you read stats back with) and it lives alongside everything you do
2878
+ * with the result.
2879
+ *
2880
+ * The rest of the namespace is everything *after* the send: what happened to each
2881
+ * message, how a campaign performed, and who has unsubscribed.
2871
2882
  *
2872
2883
  * The point is that you can build your own email admin screen inside your own app
2873
2884
  * and never open the MindStudio dashboard. So this deliberately includes the
@@ -2875,7 +2886,7 @@ interface Analytics {
2875
2886
  * unsubscribe list, this can do too.
2876
2887
  *
2877
2888
  * Every call is scoped to the executing app by its request token; there is no
2878
- * appId parameter and no way to read another app's mail.
2889
+ * appId parameter and no way to read or send as another app.
2879
2890
  */
2880
2891
  /** Where a message got to. See `EmailMessage.status`. */
2881
2892
  type EmailMessageStatus =
@@ -3011,7 +3022,149 @@ interface EmailWindowOptions {
3011
3022
  start?: string | Date;
3012
3023
  end?: string | Date;
3013
3024
  }
3025
+ /**
3026
+ * How a message body is interpreted.
3027
+ *
3028
+ * `auto` (the default) sniffs it: something that looks like HTML is sent as HTML,
3029
+ * anything else is rendered from Markdown. Every message goes out multipart with a
3030
+ * plain-text alternative derived from the body, which mail providers reward.
3031
+ */
3032
+ type EmailBodyType = 'auto' | 'html' | 'markdown' | 'text';
3033
+ interface EmailAttachmentInput {
3034
+ url: string;
3035
+ /** Displayed filename. Derived from the URL when omitted. */
3036
+ filename?: string;
3037
+ contentType?: string;
3038
+ }
3039
+ interface EmailSendOptions {
3040
+ /** Visible recipients. Optional only if you supply `cc` or `bcc`. */
3041
+ to?: string | string[];
3042
+ cc?: string | string[];
3043
+ /**
3044
+ * Hidden recipients. With no `to`/`cc`, the To: header is addressed to your
3045
+ * app's own sender — the standard "undisclosed recipients" pattern — so
3046
+ * recipients can't see each other.
3047
+ */
3048
+ bcc?: string | string[];
3049
+ subject: string;
3050
+ /** Plain text, Markdown, or HTML. See {@link EmailBodyType}. */
3051
+ body: string;
3052
+ bodyType?: EmailBodyType;
3053
+ /** Your own plain-text alternative, instead of the auto-derived one. */
3054
+ text?: string;
3055
+ attachments?: Array<string | EmailAttachmentInput>;
3056
+ /**
3057
+ * Sender handle — only if your app has a custom domain or platform subdomain.
3058
+ * `"support"`, `"support@your-domain.com"`, or `"Name <support@your-domain.com>"`.
3059
+ * The domain must be one your app owns or the send fails.
3060
+ */
3061
+ from?: string;
3062
+ replyTo?: string;
3063
+ /** Message-ID this replies to, for threading in a shared inbox. */
3064
+ inReplyTo?: string;
3065
+ /** Prior Message-IDs in the thread. */
3066
+ references?: string[];
3067
+ /**
3068
+ * **Defaults to `marketing`**, which is the consequential choice here.
3069
+ *
3070
+ * `marketing` attaches a one-click unsubscribe header pointing at your own
3071
+ * domain and skips anyone who previously unsubscribed from your app. Because
3072
+ * that link is per-recipient, a marketing send delivers one separate message per
3073
+ * recipient and `cc`/`bcc` are folded into that list — so they come back empty.
3074
+ *
3075
+ * `transactional` is for receipts, alerts and password resets: no unsubscribe
3076
+ * header, the unsubscribe list is ignored, and `cc`/`bcc` stay on one shared
3077
+ * message. Use it whenever cc/bcc semantics actually matter.
3078
+ */
3079
+ category?: 'transactional' | 'marketing';
3080
+ /**
3081
+ * Groups this send's per-recipient rows into one blast. Pass your own campaign
3082
+ * id (1–64 printable chars) to join our delivery stats onto your own tables with
3083
+ * no mapping; one is generated when omitted, and it's always returned.
3084
+ */
3085
+ batchId?: string;
3086
+ }
3087
+ interface EmailSendResult {
3088
+ /** Addresses the message went to. */
3089
+ recipients: string[];
3090
+ /** Always empty for a marketing send — see `category`. */
3091
+ cc: string[];
3092
+ /** Always empty for a marketing send — see `category`. */
3093
+ bcc: string[];
3094
+ /** The resolved sender it went out as. */
3095
+ from: string;
3096
+ /**
3097
+ * Recipients skipped because they had unsubscribed from your app. **Not an
3098
+ * error** — the send succeeded and we honoured their opt-out. Always empty for
3099
+ * a transactional send.
3100
+ *
3101
+ * Worth surfacing to whoever triggered the send: "sent to 48 of 50, 2
3102
+ * unsubscribed" is the honest report, and silence here is what makes people
3103
+ * think mail vanished.
3104
+ */
3105
+ suppressed: string[];
3106
+ /** Read stats back with `email.batch(batchId)`. */
3107
+ batchId: string;
3108
+ }
3109
+ /** Your app's sending allowance for the current window. */
3110
+ interface EmailQuota {
3111
+ /** What the numbers count — `recipients`. */
3112
+ unit: string;
3113
+ window: 'day' | 'month' | 'total';
3114
+ /** `null` means unlimited. */
3115
+ limit: number | null;
3116
+ used: number;
3117
+ /** `null` when `limit` is null. */
3118
+ remaining: number | null;
3119
+ /** When `used` resets. */
3120
+ resetsAt: string | null;
3121
+ }
3014
3122
  interface Email {
3123
+ /**
3124
+ * Send an email.
3125
+ *
3126
+ * Two things to know before your first call. **`category` defaults to
3127
+ * `marketing`**, which fans a multi-recipient send out into one message each and
3128
+ * empties `cc`/`bcc` — pass `'transactional'` for receipts, alerts and codes.
3129
+ * And a non-empty `suppressed` in the result is a **success**, not a failure: it
3130
+ * lists people who had unsubscribed and were therefore skipped.
3131
+ *
3132
+ * Delivery is synchronous today: this resolves once the mail has been handed to
3133
+ * the provider, so a large marketing list takes a while. Outcomes (delivered,
3134
+ * bounced, complained) arrive later — read them with
3135
+ * `email.messages({ batchId })`.
3136
+ *
3137
+ * @example
3138
+ * ```ts
3139
+ * // A receipt: one message, cc preserved, no unsubscribe header.
3140
+ * await email.send({
3141
+ * to: 'customer@example.com',
3142
+ * cc: 'billing@your-co.com',
3143
+ * subject: 'Your receipt',
3144
+ * body: '# Thanks!\n\nOrder #1234 is confirmed.',
3145
+ * category: 'transactional',
3146
+ * });
3147
+ *
3148
+ * // A campaign: per-recipient unsubscribe, your own id for joining stats.
3149
+ * const { suppressed, batchId } = await email.send({
3150
+ * to: subscribers,
3151
+ * subject: 'August newsletter',
3152
+ * body: markdown,
3153
+ * batchId: 'newsletter-2026-08',
3154
+ * });
3155
+ * console.log(`skipped ${suppressed.length} unsubscribed`);
3156
+ * const stats = await email.batch(batchId);
3157
+ * ```
3158
+ */
3159
+ send(options: EmailSendOptions): Promise<EmailSendResult>;
3160
+ /**
3161
+ * Your app's sending allowance and how much is left.
3162
+ *
3163
+ * Useful before a large send: check `remaining` and split the list rather than
3164
+ * discovering the limit as a failure partway through. Going over throws with
3165
+ * `outbound_daily_cap_exceeded`.
3166
+ */
3167
+ quota(): Promise<EmailQuota>;
3015
3168
  /** Counts, rates and a timeseries over a window (default: last 24h). */
3016
3169
  stats(options?: EmailWindowOptions & {
3017
3170
  buckets?: number;
@@ -3731,13 +3884,25 @@ declare class MindStudioAgent$1 {
3731
3884
  */
3732
3885
  get analytics(): Analytics;
3733
3886
  /**
3734
- * Outbound email: the per-recipient delivery log, blast stats, and this app's
3735
- * unsubscribe list. Sending is the `sendEmail` action, not this namespace.
3887
+ * Outbound email: sending, the per-recipient delivery log, blast stats, and
3888
+ * this app's unsubscribe list.
3889
+ *
3890
+ * `email.send()` is the send path — the `sendEmail` action still works and runs
3891
+ * the same code, but `send()` returns a receipt including the `batchId` you read
3892
+ * stats back with. Note `category` defaults to `'marketing'`; see
3893
+ * {@link Email.send}.
3736
3894
  *
3737
3895
  * @example
3738
3896
  * ```ts
3739
3897
  * import { email } from '@mindstudio-ai/agent';
3740
3898
  *
3899
+ * await email.send({
3900
+ * to: 'a@b.com',
3901
+ * subject: 'Your receipt',
3902
+ * body: 'Thanks!',
3903
+ * category: 'transactional',
3904
+ * });
3905
+ *
3741
3906
  * // A support lookup: did this person get their mail?
3742
3907
  * const { messages } = await email.messages({ recipient: 'a@b.com' });
3743
3908
  *
@@ -11198,11 +11363,15 @@ declare const voice: Voice;
11198
11363
  */
11199
11364
  declare const analytics: Analytics;
11200
11365
  /**
11201
- * Your app's outbound email — the delivery log, blast stats, and the unsubscribe
11202
- * list. Sending is `mindstudio.sendEmail(...)`; this is everything after it.
11366
+ * Your app's outbound email — sending, the delivery log, blast stats, and the
11367
+ * unsubscribe list.
11368
+ *
11369
+ * `email.send()` is the send path. `mindstudio.sendEmail(...)` still works and
11370
+ * runs the same code underneath, but `send()` returns a proper receipt and sits
11371
+ * next to everything you do with the result.
11203
11372
  *
11204
11373
  * Scoped to the executing app by its request token, so there is no appId to pass
11205
- * and no way to read another app's mail.
11374
+ * and no way to read or send as another app.
11206
11375
  *
11207
11376
  * @example
11208
11377
  * ```ts
package/dist/index.js CHANGED
@@ -2592,6 +2592,7 @@ function toBase64(content) {
2592
2592
  typeof content === "string" ? Buffer.from(content) : content
2593
2593
  ).toString("base64");
2594
2594
  }
2595
+ var PRESIGN_THRESHOLD = 4 * 1024 * 1024;
2595
2596
  function extensionFor(filename) {
2596
2597
  if (!filename) {
2597
2598
  return "";
@@ -2619,16 +2620,73 @@ var Store = class {
2619
2620
  const ext = extensionFor(options?.filename);
2620
2621
  const key = options?.key ?? (options?.contentAddressed ? `${createHash("sha256").update(content).digest("hex")}${ext}` : `${randomUUID()}${ext}`);
2621
2622
  const cacheControl = options?.cacheControl ?? (!options?.key && this._access === "public" ? "public, max-age=31536000, immutable" : void 0);
2623
+ const bytes = typeof content === "string" ? Buffer.from(content) : content;
2624
+ if (bytes.byteLength >= PRESIGN_THRESHOLD) {
2625
+ return this._putPresigned(bytes, key, {
2626
+ ...options?.contentType ? { contentType: options.contentType } : {},
2627
+ ...cacheControl ? { cacheControl } : {}
2628
+ });
2629
+ }
2622
2630
  const meta = await this._call("put", {
2623
2631
  store: this._store,
2624
2632
  access: this._access,
2625
2633
  key,
2626
- body: toBase64(content),
2634
+ body: toBase64(bytes),
2627
2635
  ...options?.contentType ? { contentType: options.contentType } : {},
2628
2636
  ...cacheControl ? { cacheControl } : {}
2629
2637
  });
2630
2638
  return this._toFile(key, meta);
2631
2639
  }
2640
+ /**
2641
+ * Large-content half of {@link put}: mint a presigned POST scoped to exactly
2642
+ * these bytes and send them direct to storage — the JSON transport's body
2643
+ * limit never sees them. Same result contract as the small path; the final
2644
+ * `head` keeps the returned metadata honest (server-observed size/type/time).
2645
+ */
2646
+ async _putPresigned(bytes, key, options) {
2647
+ const res = await this._call("create-upload", {
2648
+ store: this._store,
2649
+ access: this._access,
2650
+ key,
2651
+ maxSize: bytes.byteLength,
2652
+ ...options.contentType ? { contentType: options.contentType } : {},
2653
+ ...options.cacheControl ? { cacheControl: options.cacheControl } : {}
2654
+ });
2655
+ const form = new FormData();
2656
+ for (const [k, v] of Object.entries(
2657
+ res.uploadFields
2658
+ )) {
2659
+ form.append(k, v);
2660
+ }
2661
+ const view = new Uint8Array(
2662
+ bytes.buffer,
2663
+ bytes.byteOffset,
2664
+ bytes.byteLength
2665
+ );
2666
+ form.append(
2667
+ "file",
2668
+ new Blob([view], options.contentType ? { type: options.contentType } : void 0)
2669
+ );
2670
+ const upload = await fetch(res.uploadUrl, {
2671
+ method: "POST",
2672
+ body: form
2673
+ });
2674
+ if (!upload.ok) {
2675
+ const errorText = await upload.text().catch(() => "");
2676
+ throw new MindStudioError(
2677
+ `Upload failed: ${upload.status} ${upload.statusText}${errorText ? ` \u2014 ${errorText.replace(/<[^>]*>/g, " ").replace(/\s+/g, " ").trim().slice(0, 200)}` : ""}`,
2678
+ "upload_error",
2679
+ upload.status,
2680
+ errorText || void 0
2681
+ );
2682
+ }
2683
+ const meta = await this._call("head", {
2684
+ store: this._store,
2685
+ access: this._access,
2686
+ key
2687
+ });
2688
+ return this._toFile(key, meta);
2689
+ }
2632
2690
  /** Read an object's bytes (backend / trusted context). */
2633
2691
  async get(key) {
2634
2692
  const res = await this._call("get", {
@@ -2968,6 +3026,12 @@ function createAnalytics(call) {
2968
3026
  var iso = (v) => v === void 0 ? void 0 : v instanceof Date ? v.toISOString() : v;
2969
3027
  function createEmail(call) {
2970
3028
  return {
3029
+ send(options) {
3030
+ return call("send", options);
3031
+ },
3032
+ quota() {
3033
+ return call("quota", {});
3034
+ },
2971
3035
  stats(options) {
2972
3036
  return call("stats", {
2973
3037
  start: iso(options?.start),
@@ -6766,13 +6830,25 @@ var MindStudioAgent = class {
6766
6830
  );
6767
6831
  }
6768
6832
  /**
6769
- * Outbound email: the per-recipient delivery log, blast stats, and this app's
6770
- * unsubscribe list. Sending is the `sendEmail` action, not this namespace.
6833
+ * Outbound email: sending, the per-recipient delivery log, blast stats, and
6834
+ * this app's unsubscribe list.
6835
+ *
6836
+ * `email.send()` is the send path — the `sendEmail` action still works and runs
6837
+ * the same code, but `send()` returns a receipt including the `batchId` you read
6838
+ * stats back with. Note `category` defaults to `'marketing'`; see
6839
+ * {@link Email.send}.
6771
6840
  *
6772
6841
  * @example
6773
6842
  * ```ts
6774
6843
  * import { email } from '@mindstudio-ai/agent';
6775
6844
  *
6845
+ * await email.send({
6846
+ * to: 'a@b.com',
6847
+ * subject: 'Your receipt',
6848
+ * body: 'Thanks!',
6849
+ * category: 'transactional',
6850
+ * });
6851
+ *
6776
6852
  * // A support lookup: did this person get their mail?
6777
6853
  * const { messages } = await email.messages({ recipient: 'a@b.com' });
6778
6854
  *