@ragable/sdk 0.7.7 → 0.7.8

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
@@ -3603,6 +3863,63 @@ var RagableBrowserAgentsClient = class {
3603
3863
  const source = this.runStreamParts(agentName, params);
3604
3864
  return createStreamResultFromParts(source);
3605
3865
  }
3866
+ /**
3867
+ * Same agent, same tools/instructions/RAG — but constrain the final output
3868
+ * to a JSON Schema. Matches `client.ai.streamObject` exactly so callers can
3869
+ * swap between raw inference and an agent without changing call shape.
3870
+ *
3871
+ * Tool calling and structured output are **compatible**: the model may call
3872
+ * the agent's tools as usual; the final assistant message is the schema-
3873
+ * conformant JSON. The agent's `agents/<name>.json` is unchanged — whether
3874
+ * the run is conversational or structured is decided by the call site.
3875
+ *
3876
+ * ```ts
3877
+ * const { partialObjectStream, object } = client.agents.runObject<Plan>(
3878
+ * "planner",
3879
+ * {
3880
+ * messages: [{ role: "user", content: "Plan a 3-day trip to Kyoto." }],
3881
+ * schema: {
3882
+ * type: "object",
3883
+ * properties: {
3884
+ * days: {
3885
+ * type: "array",
3886
+ * items: { type: "object", properties: { date: { type: "string" }, activities: { type: "array", items: { type: "string" } } } },
3887
+ * },
3888
+ * },
3889
+ * required: ["days"],
3890
+ * },
3891
+ * },
3892
+ * );
3893
+ * for await (const p of partialObjectStream) renderPreview(p);
3894
+ * console.log(await object);
3895
+ * ```
3896
+ */
3897
+ runObject(agentName, params) {
3898
+ const responseFormat = buildResponseFormat({
3899
+ schema: params.schema,
3900
+ ...params.schemaName !== void 0 ? { name: params.schemaName } : {},
3901
+ ...params.schemaDescription !== void 0 ? { description: params.schemaDescription } : {}
3902
+ });
3903
+ const sourceParts = this.runStreamParts(agentName, {
3904
+ messages: params.messages,
3905
+ ...params.signal !== void 0 ? { signal: params.signal } : {},
3906
+ responseFormat
3907
+ });
3908
+ const inner = createStreamResultFromParts(sourceParts);
3909
+ return wrapStreamTextAsObject(inner);
3910
+ }
3911
+ /** Non-streaming variant of {@link runObject}. */
3912
+ async generateObject(agentName, params) {
3913
+ const result = this.runObject(agentName, params);
3914
+ for await (const _ of result.partialObjectStream) void _;
3915
+ const [object, usage, finishReason, toolCalls] = await Promise.all([
3916
+ result.object,
3917
+ result.usage,
3918
+ result.finishReason,
3919
+ result.toolCalls
3920
+ ]);
3921
+ return { object, usage, finishReason, toolCalls };
3922
+ }
3606
3923
  async *runStreamParts(agentName, params) {
3607
3924
  const { messages } = params;
3608
3925
  if (!Array.isArray(messages) || messages.length === 0) {
@@ -3624,12 +3941,33 @@ var RagableBrowserAgentsClient = class {
3624
3941
  role: m.role,
3625
3942
  content: m.content
3626
3943
  }));
3627
- const events = this.chatStreamByName(agentName, {
3944
+ const headers = new Headers(this.options.headers);
3945
+ headers.set("Content-Type", "application/json");
3946
+ const body = {
3628
3947
  message: last.content,
3629
3948
  ...history.length > 0 ? { history } : {},
3630
- ...params.signal !== void 0 ? { signal: params.signal } : {}
3631
- });
3632
- for await (const event of events) {
3949
+ ...params.responseFormat ? { response_format: params.responseFormat } : {}
3950
+ };
3951
+ const response = await this.fetchImpl(
3952
+ this.toUrl(
3953
+ this.websiteAgentPath(
3954
+ `/agents/${encodeURIComponent(agentName)}/chat/stream`
3955
+ )
3956
+ ),
3957
+ {
3958
+ method: "POST",
3959
+ headers,
3960
+ body: JSON.stringify(body),
3961
+ ...params.signal !== void 0 ? { signal: params.signal } : {}
3962
+ }
3963
+ );
3964
+ if (!response.ok) {
3965
+ const payload = await parseMaybeJsonBody(response);
3966
+ const message = extractErrorMessage(payload, response.statusText);
3967
+ throw new RagableError(message, response.status, payload);
3968
+ }
3969
+ if (!response.body) return;
3970
+ for await (const event of readSseStream(response.body)) {
3633
3971
  const mapped = mapAgentEvent(event);
3634
3972
  if (mapped) yield mapped;
3635
3973
  }
@@ -3866,6 +4204,7 @@ export {
3866
4204
  assertPostgrestSuccess,
3867
4205
  bindFetch,
3868
4206
  buildInferenceRequestBody,
4207
+ buildResponseFormat,
3869
4208
  collectAssistantTextFromUiSegments,
3870
4209
  collectionRecordToRowWithMeta,
3871
4210
  collectionRecordsToRowWithMeta,
@@ -3893,7 +4232,10 @@ export {
3893
4232
  runAgentChatStream,
3894
4233
  runAgentChatStreamForUi,
3895
4234
  runAgentChatStreamLenient,
4235
+ streamObjectFromContext,
3896
4236
  toRagableResult,
3897
- unwrapPostgrest
4237
+ tryParsePartialJson,
4238
+ unwrapPostgrest,
4239
+ wrapStreamTextAsObject
3898
4240
  };
3899
4241
  //# sourceMappingURL=index.mjs.map