@ragable/sdk 0.7.7 → 0.7.9

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.mjs CHANGED
@@ -2269,6 +2269,72 @@ function decodeJwtExpiry(jwt) {
2269
2269
  }
2270
2270
  }
2271
2271
 
2272
+ // src/partial-json.ts
2273
+ function tryParsePartialJson(text) {
2274
+ const trimmed = text.trim();
2275
+ if (!trimmed) return void 0;
2276
+ try {
2277
+ return JSON.parse(trimmed);
2278
+ } catch {
2279
+ }
2280
+ const repaired = repairOpenStructures(trimmed);
2281
+ if (!repaired) return void 0;
2282
+ try {
2283
+ return JSON.parse(repaired);
2284
+ } catch {
2285
+ const noTrailingComma = stripTrailingCommas(trimmed);
2286
+ const repaired2 = repairOpenStructures(noTrailingComma);
2287
+ if (!repaired2) return void 0;
2288
+ try {
2289
+ return JSON.parse(repaired2);
2290
+ } catch {
2291
+ return void 0;
2292
+ }
2293
+ }
2294
+ }
2295
+ function repairOpenStructures(text) {
2296
+ const first = text[0];
2297
+ if (first !== "{" && first !== "[") {
2298
+ return text;
2299
+ }
2300
+ const stack = [];
2301
+ let inString = false;
2302
+ let escaped = false;
2303
+ for (let i = 0; i < text.length; i++) {
2304
+ const ch = text[i];
2305
+ if (escaped) {
2306
+ escaped = false;
2307
+ continue;
2308
+ }
2309
+ if (ch === "\\") {
2310
+ escaped = true;
2311
+ continue;
2312
+ }
2313
+ if (ch === '"') {
2314
+ inString = !inString;
2315
+ continue;
2316
+ }
2317
+ if (inString) continue;
2318
+ if (ch === "{") stack.push("}");
2319
+ else if (ch === "[") stack.push("]");
2320
+ else if (ch === "}" || ch === "]") {
2321
+ if (stack.length === 0) return null;
2322
+ stack.pop();
2323
+ }
2324
+ }
2325
+ let safe = text;
2326
+ if (inString) {
2327
+ safe += '"';
2328
+ }
2329
+ safe = safe.replace(/,\s*$/, "").replace(/:\s*$/, ": null");
2330
+ let suffix = "";
2331
+ for (let i = stack.length - 1; i >= 0; i--) suffix += stack[i];
2332
+ return safe + suffix;
2333
+ }
2334
+ function stripTrailingCommas(text) {
2335
+ return text.replace(/,(\s*[}\]])/g, "$1").replace(/,\s*$/, "");
2336
+ }
2337
+
2272
2338
  // src/stream-parts.ts
2273
2339
  function normalizeFinishReason(raw) {
2274
2340
  switch (raw) {
@@ -2478,7 +2544,7 @@ var ZERO_USAGE = {
2478
2544
  completionTokens: 0,
2479
2545
  totalTokens: 0
2480
2546
  };
2481
- function buildInferenceRequestBody(params) {
2547
+ function buildInferenceRequestBody(params, responseFormat) {
2482
2548
  const body = {
2483
2549
  model: params.model,
2484
2550
  messages: params.messages
@@ -2489,8 +2555,18 @@ function buildInferenceRequestBody(params) {
2489
2555
  if (typeof params.maxTokens === "number") body.max_tokens = params.maxTokens;
2490
2556
  if (typeof params.topP === "number") body.top_p = params.topP;
2491
2557
  if (params.reasoningEffort) body.reasoning_effort = params.reasoningEffort;
2558
+ if (responseFormat) body.response_format = responseFormat;
2492
2559
  return body;
2493
2560
  }
2561
+ function buildResponseFormat(params) {
2562
+ const json_schema = {
2563
+ name: params.name ?? "Output",
2564
+ schema: params.schema,
2565
+ strict: params.strict ?? true
2566
+ };
2567
+ if (params.description) json_schema.description = params.description;
2568
+ return { type: "json_schema", json_schema };
2569
+ }
2494
2570
  async function consumeInferenceStream(body, broadcast, deferreds) {
2495
2571
  const reader = body.getReader();
2496
2572
  const decoder = new TextDecoder();
@@ -2741,6 +2817,190 @@ var RagableBrowserAiClient = class {
2741
2817
  );
2742
2818
  return { text, reasoning, usage, finishReason, toolCalls };
2743
2819
  }
2820
+ /**
2821
+ * Stream a JSON-Schema-constrained response. Matches Vercel AI SDK's
2822
+ * `streamObject` shape — returns a synchronous result with `partialObjectStream`
2823
+ * (best-effort incremental parses) and `object` (the final parsed JSON).
2824
+ *
2825
+ * ```ts
2826
+ * const { partialObjectStream, object } = client.ai.streamObject({
2827
+ * model: "accounts/fireworks/models/kimi-k2p5",
2828
+ * schema: {
2829
+ * type: "object",
2830
+ * properties: {
2831
+ * title: { type: "string" },
2832
+ * tags: { type: "array", items: { type: "string" } },
2833
+ * },
2834
+ * required: ["title", "tags"],
2835
+ * },
2836
+ * messages: [{ role: "user", content: "Give me a blog post idea about AI." }],
2837
+ * });
2838
+ * for await (const partial of partialObjectStream) renderPreview(partial);
2839
+ * const final = await object;
2840
+ * ```
2841
+ */
2842
+ streamObject(params) {
2843
+ return streamObjectFromContext(this.buildContext(), params);
2844
+ }
2845
+ /**
2846
+ * Non-streaming variant of {@link streamObject}. Resolves once the model
2847
+ * finishes; rejects if the final text isn't valid JSON for the schema.
2848
+ */
2849
+ async generateObject(params) {
2850
+ const result = this.streamObject(params);
2851
+ for await (const _ of result.partialObjectStream) {
2852
+ void _;
2853
+ }
2854
+ const [object, usage, finishReason, toolCalls] = await Promise.all([
2855
+ result.object,
2856
+ result.usage,
2857
+ result.finishReason,
2858
+ result.toolCalls
2859
+ ]);
2860
+ return { object, usage, finishReason, toolCalls };
2861
+ }
2862
+ };
2863
+ function streamObjectFromContext(ctx, params) {
2864
+ const textParams = {
2865
+ model: params.model,
2866
+ messages: params.messages,
2867
+ ...params.system !== void 0 ? { system: params.system } : {},
2868
+ ...typeof params.temperature === "number" ? { temperature: params.temperature } : {},
2869
+ ...typeof params.maxTokens === "number" ? { maxTokens: params.maxTokens } : {},
2870
+ ...typeof params.topP === "number" ? { topP: params.topP } : {},
2871
+ ...params.signal !== void 0 ? { signal: params.signal } : {}
2872
+ };
2873
+ const responseFormat = buildResponseFormat({
2874
+ schema: params.schema,
2875
+ ...params.schemaName !== void 0 ? { name: params.schemaName } : {},
2876
+ ...params.schemaDescription !== void 0 ? { description: params.schemaDescription } : {}
2877
+ });
2878
+ const overrideCtx = {
2879
+ ...ctx,
2880
+ // Wrap fetch to substitute the body. We can't change buildInferenceRequestBody
2881
+ // signature on the call site cleanly, so intercept here.
2882
+ fetch: ((input, init) => {
2883
+ if (init && typeof init.body === "string") {
2884
+ const parsed = JSON.parse(init.body);
2885
+ parsed.response_format = responseFormat;
2886
+ const newInit = { ...init, body: JSON.stringify(parsed) };
2887
+ return ctx.fetch(input, newInit);
2888
+ }
2889
+ return ctx.fetch(input, init);
2890
+ })
2891
+ };
2892
+ const inner = streamInferenceFromContext(overrideCtx, textParams);
2893
+ return wrapStreamTextAsObject(inner);
2894
+ }
2895
+ function wrapStreamTextAsObject(inner) {
2896
+ const partialBroadcast = new PartialObjectBroadcast();
2897
+ const objectDeferred = defer();
2898
+ void (async () => {
2899
+ let acc = "";
2900
+ let lastEmitted = /* @__PURE__ */ Symbol("none");
2901
+ try {
2902
+ for await (const delta of inner.textStream) {
2903
+ acc += delta;
2904
+ const candidate = tryParsePartialJson(acc);
2905
+ if (candidate !== void 0 && !sameSnapshot(candidate, lastEmitted)) {
2906
+ lastEmitted = candidate;
2907
+ partialBroadcast.push(candidate);
2908
+ }
2909
+ }
2910
+ const finalText = await inner.text;
2911
+ let finalObj;
2912
+ try {
2913
+ finalObj = JSON.parse(finalText);
2914
+ } catch (e) {
2915
+ const err = new RagableError(
2916
+ `Model output is not valid JSON: ${e.message}`,
2917
+ 502,
2918
+ {
2919
+ code: "SDK_OBJECT_PARSE_FAILED",
2920
+ raw: finalText.slice(0, 1e3)
2921
+ }
2922
+ );
2923
+ partialBroadcast.fail(err);
2924
+ objectDeferred.reject(err);
2925
+ return;
2926
+ }
2927
+ if (!sameSnapshot(finalObj, lastEmitted)) {
2928
+ partialBroadcast.push(finalObj);
2929
+ }
2930
+ partialBroadcast.end();
2931
+ objectDeferred.resolve(finalObj);
2932
+ } catch (err) {
2933
+ partialBroadcast.fail(err);
2934
+ objectDeferred.reject(err);
2935
+ }
2936
+ })();
2937
+ return {
2938
+ textStream: inner.textStream,
2939
+ partialObjectStream: partialBroadcast.consume(),
2940
+ object: objectDeferred.promise,
2941
+ text: inner.text,
2942
+ usage: inner.usage,
2943
+ finishReason: inner.finishReason,
2944
+ toolCalls: inner.toolCalls
2945
+ };
2946
+ }
2947
+ function sameSnapshot(a, b) {
2948
+ if (a === b) return true;
2949
+ try {
2950
+ return JSON.stringify(a) === JSON.stringify(b);
2951
+ } catch {
2952
+ return false;
2953
+ }
2954
+ }
2955
+ var PartialObjectBroadcast = class {
2956
+ constructor() {
2957
+ __publicField(this, "items", []);
2958
+ __publicField(this, "resolved", false);
2959
+ __publicField(this, "error", null);
2960
+ __publicField(this, "waiters", []);
2961
+ }
2962
+ push(item) {
2963
+ this.items.push(item);
2964
+ this.notify();
2965
+ }
2966
+ end() {
2967
+ if (this.resolved) return;
2968
+ this.resolved = true;
2969
+ this.notify();
2970
+ }
2971
+ fail(error) {
2972
+ if (this.resolved) return;
2973
+ this.error = error;
2974
+ this.resolved = true;
2975
+ this.notify();
2976
+ }
2977
+ notify() {
2978
+ const w = this.waiters;
2979
+ this.waiters = [];
2980
+ for (const fn of w) fn();
2981
+ }
2982
+ consume() {
2983
+ const self = this;
2984
+ return {
2985
+ [Symbol.asyncIterator]: () => {
2986
+ let idx = 0;
2987
+ return {
2988
+ next: async () => {
2989
+ while (true) {
2990
+ if (idx < self.items.length) {
2991
+ return { value: self.items[idx++], done: false };
2992
+ }
2993
+ if (self.resolved) {
2994
+ if (self.error) throw self.error;
2995
+ return { value: void 0, done: true };
2996
+ }
2997
+ await new Promise((res) => self.waiters.push(res));
2998
+ }
2999
+ }
3000
+ };
3001
+ }
3002
+ };
3003
+ }
2744
3004
  };
2745
3005
 
2746
3006
  // src/browser.ts
@@ -3522,6 +3782,120 @@ var RagableBrowserStorageClient = class {
3522
3782
  return new BrowserStorageBucketClient(this.options, this.fetchImpl, bucketId);
3523
3783
  }
3524
3784
  };
3785
+ var RagableBrowserMailClient = class {
3786
+ constructor(options, auth) {
3787
+ this.options = options;
3788
+ this.auth = auth;
3789
+ __publicField(this, "fetchImpl");
3790
+ this.fetchImpl = bindFetch(options.fetch);
3791
+ }
3792
+ requireWebsiteId() {
3793
+ const websiteId = this.options.websiteId?.trim();
3794
+ if (!websiteId) {
3795
+ throw new RagableError(
3796
+ "websiteId is required for mail operations. Use createWebsiteRagableClient() or pass createBrowserClient({ websiteId, ... }).",
3797
+ 400,
3798
+ { code: "SDK_MISSING_WEBSITE_ID" }
3799
+ );
3800
+ }
3801
+ return websiteId;
3802
+ }
3803
+ pathTo(p) {
3804
+ const websiteId = this.requireWebsiteId();
3805
+ const orgId = this.options.organizationId;
3806
+ return `${normalizeBrowserApiBase()}/public/organizations/${orgId}/websites/${websiteId}/mail${p.startsWith("/") ? p : `/${p}`}`;
3807
+ }
3808
+ /**
3809
+ * Get the Bearer token used to authenticate the call:
3810
+ * 1. End-user access token (preferred when an auth group is configured
3811
+ * and the user has signed in)
3812
+ * 2. `dataStaticKey` from createBrowserClient options
3813
+ * 3. Caller-supplied `getAccessToken()`
3814
+ *
3815
+ * If none is available, throws — server-side use should pass the
3816
+ * data-admin key as `dataStaticKey`.
3817
+ */
3818
+ async getBearerToken() {
3819
+ if (this.auth) {
3820
+ const token = await this.auth.getValidAccessToken().catch(() => null);
3821
+ if (token) return token;
3822
+ }
3823
+ const callerProvided = await this.options.getAccessToken?.();
3824
+ if (typeof callerProvided === "string" && callerProvided.length > 0) {
3825
+ return callerProvided;
3826
+ }
3827
+ if (this.options.dataStaticKey?.trim()) {
3828
+ return this.options.dataStaticKey.trim();
3829
+ }
3830
+ throw new RagableError(
3831
+ "Mail requests need authentication: either sign in via client.auth.signIn(...) or pass dataStaticKey (the auth group data-admin key) when creating the client.",
3832
+ 401,
3833
+ { code: "SDK_MAIL_NOT_AUTHENTICATED" }
3834
+ );
3835
+ }
3836
+ async request(path, init = {}) {
3837
+ const token = await this.getBearerToken();
3838
+ const headers = new Headers(init.headers ?? this.options.headers);
3839
+ headers.set("Authorization", `Bearer ${token}`);
3840
+ if (init.body && !headers.has("Content-Type")) {
3841
+ headers.set("Content-Type", "application/json");
3842
+ }
3843
+ const response = await this.fetchImpl(this.pathTo(path), {
3844
+ ...init,
3845
+ headers
3846
+ });
3847
+ const payload = await parseMaybeJsonBody(response);
3848
+ if (!response.ok) {
3849
+ const message = extractErrorMessage(payload, response.statusText);
3850
+ throw new RagableError(message, response.status, payload);
3851
+ }
3852
+ return payload;
3853
+ }
3854
+ /** Send an email from this website's linked Gmail account. */
3855
+ async send(params) {
3856
+ if (!params.to?.length) {
3857
+ throw new RagableError(
3858
+ "`to` must contain at least one recipient.",
3859
+ 400,
3860
+ { code: "SDK_MAIL_NO_RECIPIENTS" }
3861
+ );
3862
+ }
3863
+ if (!params.bodyText && !params.bodyHtml) {
3864
+ throw new RagableError(
3865
+ "Provide at least one of `bodyText` or `bodyHtml`.",
3866
+ 400,
3867
+ { code: "SDK_MAIL_NO_BODY" }
3868
+ );
3869
+ }
3870
+ return this.request("/send", {
3871
+ method: "POST",
3872
+ body: JSON.stringify(params)
3873
+ });
3874
+ }
3875
+ /** Search messages with Gmail query syntax. */
3876
+ async search(params = {}) {
3877
+ const qs = new URLSearchParams();
3878
+ if (params.query) qs.set("q", params.query);
3879
+ if (params.maxResults) qs.set("maxResults", String(params.maxResults));
3880
+ if (params.pageToken) qs.set("pageToken", params.pageToken);
3881
+ if (params.labelIds?.length) qs.set("labelIds", params.labelIds.join(","));
3882
+ const suffix = qs.toString() ? `?${qs.toString()}` : "";
3883
+ return this.request(`/search${suffix}`, { method: "GET" });
3884
+ }
3885
+ /** Fetch a single message in full (headers + decoded text/html body). */
3886
+ async getMessage(messageId) {
3887
+ if (!messageId) {
3888
+ throw new RagableError("`messageId` is required.", 400, {
3889
+ code: "SDK_MAIL_NO_MESSAGE_ID"
3890
+ });
3891
+ }
3892
+ const { message } = await this.request(
3893
+ `/messages/${encodeURIComponent(messageId)}`,
3894
+ { method: "GET" }
3895
+ );
3896
+ return message;
3897
+ }
3898
+ };
3525
3899
  var RagableBrowserAgentsClient = class {
3526
3900
  constructor(options) {
3527
3901
  this.options = options;
@@ -3603,6 +3977,63 @@ var RagableBrowserAgentsClient = class {
3603
3977
  const source = this.runStreamParts(agentName, params);
3604
3978
  return createStreamResultFromParts(source);
3605
3979
  }
3980
+ /**
3981
+ * Same agent, same tools/instructions/RAG — but constrain the final output
3982
+ * to a JSON Schema. Matches `client.ai.streamObject` exactly so callers can
3983
+ * swap between raw inference and an agent without changing call shape.
3984
+ *
3985
+ * Tool calling and structured output are **compatible**: the model may call
3986
+ * the agent's tools as usual; the final assistant message is the schema-
3987
+ * conformant JSON. The agent's `agents/<name>.json` is unchanged — whether
3988
+ * the run is conversational or structured is decided by the call site.
3989
+ *
3990
+ * ```ts
3991
+ * const { partialObjectStream, object } = client.agents.runObject<Plan>(
3992
+ * "planner",
3993
+ * {
3994
+ * messages: [{ role: "user", content: "Plan a 3-day trip to Kyoto." }],
3995
+ * schema: {
3996
+ * type: "object",
3997
+ * properties: {
3998
+ * days: {
3999
+ * type: "array",
4000
+ * items: { type: "object", properties: { date: { type: "string" }, activities: { type: "array", items: { type: "string" } } } },
4001
+ * },
4002
+ * },
4003
+ * required: ["days"],
4004
+ * },
4005
+ * },
4006
+ * );
4007
+ * for await (const p of partialObjectStream) renderPreview(p);
4008
+ * console.log(await object);
4009
+ * ```
4010
+ */
4011
+ runObject(agentName, params) {
4012
+ const responseFormat = buildResponseFormat({
4013
+ schema: params.schema,
4014
+ ...params.schemaName !== void 0 ? { name: params.schemaName } : {},
4015
+ ...params.schemaDescription !== void 0 ? { description: params.schemaDescription } : {}
4016
+ });
4017
+ const sourceParts = this.runStreamParts(agentName, {
4018
+ messages: params.messages,
4019
+ ...params.signal !== void 0 ? { signal: params.signal } : {},
4020
+ responseFormat
4021
+ });
4022
+ const inner = createStreamResultFromParts(sourceParts);
4023
+ return wrapStreamTextAsObject(inner);
4024
+ }
4025
+ /** Non-streaming variant of {@link runObject}. */
4026
+ async generateObject(agentName, params) {
4027
+ const result = this.runObject(agentName, params);
4028
+ for await (const _ of result.partialObjectStream) void _;
4029
+ const [object, usage, finishReason, toolCalls] = await Promise.all([
4030
+ result.object,
4031
+ result.usage,
4032
+ result.finishReason,
4033
+ result.toolCalls
4034
+ ]);
4035
+ return { object, usage, finishReason, toolCalls };
4036
+ }
3606
4037
  async *runStreamParts(agentName, params) {
3607
4038
  const { messages } = params;
3608
4039
  if (!Array.isArray(messages) || messages.length === 0) {
@@ -3624,12 +4055,33 @@ var RagableBrowserAgentsClient = class {
3624
4055
  role: m.role,
3625
4056
  content: m.content
3626
4057
  }));
3627
- const events = this.chatStreamByName(agentName, {
4058
+ const headers = new Headers(this.options.headers);
4059
+ headers.set("Content-Type", "application/json");
4060
+ const body = {
3628
4061
  message: last.content,
3629
4062
  ...history.length > 0 ? { history } : {},
3630
- ...params.signal !== void 0 ? { signal: params.signal } : {}
3631
- });
3632
- for await (const event of events) {
4063
+ ...params.responseFormat ? { response_format: params.responseFormat } : {}
4064
+ };
4065
+ const response = await this.fetchImpl(
4066
+ this.toUrl(
4067
+ this.websiteAgentPath(
4068
+ `/agents/${encodeURIComponent(agentName)}/chat/stream`
4069
+ )
4070
+ ),
4071
+ {
4072
+ method: "POST",
4073
+ headers,
4074
+ body: JSON.stringify(body),
4075
+ ...params.signal !== void 0 ? { signal: params.signal } : {}
4076
+ }
4077
+ );
4078
+ if (!response.ok) {
4079
+ const payload = await parseMaybeJsonBody(response);
4080
+ const message = extractErrorMessage(payload, response.statusText);
4081
+ throw new RagableError(message, response.status, payload);
4082
+ }
4083
+ if (!response.body) return;
4084
+ for await (const event of readSseStream(response.body)) {
3633
4085
  const mapped = mapAgentEvent(event);
3634
4086
  if (mapped) yield mapped;
3635
4087
  }
@@ -3770,6 +4222,7 @@ var RagableBrowser = class {
3770
4222
  __publicField(this, "database");
3771
4223
  __publicField(this, "db");
3772
4224
  __publicField(this, "storage");
4225
+ __publicField(this, "mail");
3773
4226
  __publicField(this, "transport");
3774
4227
  __publicField(this, "_ragableAuth");
3775
4228
  /** Delegates to `database.from()`. Kept for back-compat — prefer `database.from()`. */
@@ -3815,6 +4268,7 @@ var RagableBrowser = class {
3815
4268
  this.database._setTransport(this.transport);
3816
4269
  this.db = this.database;
3817
4270
  this.storage = new RagableBrowserStorageClient(options, bindFetch(options.fetch));
4271
+ this.mail = new RagableBrowserMailClient(options, this._ragableAuth);
3818
4272
  }
3819
4273
  destroy() {
3820
4274
  this._ragableAuth?.destroy();
@@ -3855,6 +4309,7 @@ export {
3855
4309
  RagableBrowserAiClient,
3856
4310
  RagableBrowserAuthClient,
3857
4311
  RagableBrowserDatabaseClient,
4312
+ RagableBrowserMailClient,
3858
4313
  RagableBrowserStorageClient,
3859
4314
  RagableError,
3860
4315
  RagableNetworkError,
@@ -3866,6 +4321,7 @@ export {
3866
4321
  assertPostgrestSuccess,
3867
4322
  bindFetch,
3868
4323
  buildInferenceRequestBody,
4324
+ buildResponseFormat,
3869
4325
  collectAssistantTextFromUiSegments,
3870
4326
  collectionRecordToRowWithMeta,
3871
4327
  collectionRecordsToRowWithMeta,
@@ -3893,7 +4349,10 @@ export {
3893
4349
  runAgentChatStream,
3894
4350
  runAgentChatStreamForUi,
3895
4351
  runAgentChatStreamLenient,
4352
+ streamObjectFromContext,
3896
4353
  toRagableResult,
3897
- unwrapPostgrest
4354
+ tryParsePartialJson,
4355
+ unwrapPostgrest,
4356
+ wrapStreamTextAsObject
3898
4357
  };
3899
4358
  //# sourceMappingURL=index.mjs.map