@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.js CHANGED
@@ -58,6 +58,7 @@ __export(index_exports, {
58
58
  assertPostgrestSuccess: () => assertPostgrestSuccess,
59
59
  bindFetch: () => bindFetch,
60
60
  buildInferenceRequestBody: () => buildInferenceRequestBody,
61
+ buildResponseFormat: () => buildResponseFormat,
61
62
  collectAssistantTextFromUiSegments: () => collectAssistantTextFromUiSegments,
62
63
  collectionRecordToRowWithMeta: () => collectionRecordToRowWithMeta,
63
64
  collectionRecordsToRowWithMeta: () => collectionRecordsToRowWithMeta,
@@ -85,8 +86,11 @@ __export(index_exports, {
85
86
  runAgentChatStream: () => runAgentChatStream,
86
87
  runAgentChatStreamForUi: () => runAgentChatStreamForUi,
87
88
  runAgentChatStreamLenient: () => runAgentChatStreamLenient,
89
+ streamObjectFromContext: () => streamObjectFromContext,
88
90
  toRagableResult: () => toRagableResult,
89
- unwrapPostgrest: () => unwrapPostgrest
91
+ tryParsePartialJson: () => tryParsePartialJson,
92
+ unwrapPostgrest: () => unwrapPostgrest,
93
+ wrapStreamTextAsObject: () => wrapStreamTextAsObject
90
94
  });
91
95
  module.exports = __toCommonJS(index_exports);
92
96
 
@@ -2357,6 +2361,72 @@ function decodeJwtExpiry(jwt) {
2357
2361
  }
2358
2362
  }
2359
2363
 
2364
+ // src/partial-json.ts
2365
+ function tryParsePartialJson(text) {
2366
+ const trimmed = text.trim();
2367
+ if (!trimmed) return void 0;
2368
+ try {
2369
+ return JSON.parse(trimmed);
2370
+ } catch {
2371
+ }
2372
+ const repaired = repairOpenStructures(trimmed);
2373
+ if (!repaired) return void 0;
2374
+ try {
2375
+ return JSON.parse(repaired);
2376
+ } catch {
2377
+ const noTrailingComma = stripTrailingCommas(trimmed);
2378
+ const repaired2 = repairOpenStructures(noTrailingComma);
2379
+ if (!repaired2) return void 0;
2380
+ try {
2381
+ return JSON.parse(repaired2);
2382
+ } catch {
2383
+ return void 0;
2384
+ }
2385
+ }
2386
+ }
2387
+ function repairOpenStructures(text) {
2388
+ const first = text[0];
2389
+ if (first !== "{" && first !== "[") {
2390
+ return text;
2391
+ }
2392
+ const stack = [];
2393
+ let inString = false;
2394
+ let escaped = false;
2395
+ for (let i = 0; i < text.length; i++) {
2396
+ const ch = text[i];
2397
+ if (escaped) {
2398
+ escaped = false;
2399
+ continue;
2400
+ }
2401
+ if (ch === "\\") {
2402
+ escaped = true;
2403
+ continue;
2404
+ }
2405
+ if (ch === '"') {
2406
+ inString = !inString;
2407
+ continue;
2408
+ }
2409
+ if (inString) continue;
2410
+ if (ch === "{") stack.push("}");
2411
+ else if (ch === "[") stack.push("]");
2412
+ else if (ch === "}" || ch === "]") {
2413
+ if (stack.length === 0) return null;
2414
+ stack.pop();
2415
+ }
2416
+ }
2417
+ let safe = text;
2418
+ if (inString) {
2419
+ safe += '"';
2420
+ }
2421
+ safe = safe.replace(/,\s*$/, "").replace(/:\s*$/, ": null");
2422
+ let suffix = "";
2423
+ for (let i = stack.length - 1; i >= 0; i--) suffix += stack[i];
2424
+ return safe + suffix;
2425
+ }
2426
+ function stripTrailingCommas(text) {
2427
+ return text.replace(/,(\s*[}\]])/g, "$1").replace(/,\s*$/, "");
2428
+ }
2429
+
2360
2430
  // src/stream-parts.ts
2361
2431
  function normalizeFinishReason(raw) {
2362
2432
  switch (raw) {
@@ -2566,7 +2636,7 @@ var ZERO_USAGE = {
2566
2636
  completionTokens: 0,
2567
2637
  totalTokens: 0
2568
2638
  };
2569
- function buildInferenceRequestBody(params) {
2639
+ function buildInferenceRequestBody(params, responseFormat) {
2570
2640
  const body = {
2571
2641
  model: params.model,
2572
2642
  messages: params.messages
@@ -2577,8 +2647,18 @@ function buildInferenceRequestBody(params) {
2577
2647
  if (typeof params.maxTokens === "number") body.max_tokens = params.maxTokens;
2578
2648
  if (typeof params.topP === "number") body.top_p = params.topP;
2579
2649
  if (params.reasoningEffort) body.reasoning_effort = params.reasoningEffort;
2650
+ if (responseFormat) body.response_format = responseFormat;
2580
2651
  return body;
2581
2652
  }
2653
+ function buildResponseFormat(params) {
2654
+ const json_schema = {
2655
+ name: params.name ?? "Output",
2656
+ schema: params.schema,
2657
+ strict: params.strict ?? true
2658
+ };
2659
+ if (params.description) json_schema.description = params.description;
2660
+ return { type: "json_schema", json_schema };
2661
+ }
2582
2662
  async function consumeInferenceStream(body, broadcast, deferreds) {
2583
2663
  const reader = body.getReader();
2584
2664
  const decoder = new TextDecoder();
@@ -2829,6 +2909,190 @@ var RagableBrowserAiClient = class {
2829
2909
  );
2830
2910
  return { text, reasoning, usage, finishReason, toolCalls };
2831
2911
  }
2912
+ /**
2913
+ * Stream a JSON-Schema-constrained response. Matches Vercel AI SDK's
2914
+ * `streamObject` shape — returns a synchronous result with `partialObjectStream`
2915
+ * (best-effort incremental parses) and `object` (the final parsed JSON).
2916
+ *
2917
+ * ```ts
2918
+ * const { partialObjectStream, object } = client.ai.streamObject({
2919
+ * model: "accounts/fireworks/models/kimi-k2p5",
2920
+ * schema: {
2921
+ * type: "object",
2922
+ * properties: {
2923
+ * title: { type: "string" },
2924
+ * tags: { type: "array", items: { type: "string" } },
2925
+ * },
2926
+ * required: ["title", "tags"],
2927
+ * },
2928
+ * messages: [{ role: "user", content: "Give me a blog post idea about AI." }],
2929
+ * });
2930
+ * for await (const partial of partialObjectStream) renderPreview(partial);
2931
+ * const final = await object;
2932
+ * ```
2933
+ */
2934
+ streamObject(params) {
2935
+ return streamObjectFromContext(this.buildContext(), params);
2936
+ }
2937
+ /**
2938
+ * Non-streaming variant of {@link streamObject}. Resolves once the model
2939
+ * finishes; rejects if the final text isn't valid JSON for the schema.
2940
+ */
2941
+ async generateObject(params) {
2942
+ const result = this.streamObject(params);
2943
+ for await (const _ of result.partialObjectStream) {
2944
+ void _;
2945
+ }
2946
+ const [object, usage, finishReason, toolCalls] = await Promise.all([
2947
+ result.object,
2948
+ result.usage,
2949
+ result.finishReason,
2950
+ result.toolCalls
2951
+ ]);
2952
+ return { object, usage, finishReason, toolCalls };
2953
+ }
2954
+ };
2955
+ function streamObjectFromContext(ctx, params) {
2956
+ const textParams = {
2957
+ model: params.model,
2958
+ messages: params.messages,
2959
+ ...params.system !== void 0 ? { system: params.system } : {},
2960
+ ...typeof params.temperature === "number" ? { temperature: params.temperature } : {},
2961
+ ...typeof params.maxTokens === "number" ? { maxTokens: params.maxTokens } : {},
2962
+ ...typeof params.topP === "number" ? { topP: params.topP } : {},
2963
+ ...params.signal !== void 0 ? { signal: params.signal } : {}
2964
+ };
2965
+ const responseFormat = buildResponseFormat({
2966
+ schema: params.schema,
2967
+ ...params.schemaName !== void 0 ? { name: params.schemaName } : {},
2968
+ ...params.schemaDescription !== void 0 ? { description: params.schemaDescription } : {}
2969
+ });
2970
+ const overrideCtx = {
2971
+ ...ctx,
2972
+ // Wrap fetch to substitute the body. We can't change buildInferenceRequestBody
2973
+ // signature on the call site cleanly, so intercept here.
2974
+ fetch: ((input, init) => {
2975
+ if (init && typeof init.body === "string") {
2976
+ const parsed = JSON.parse(init.body);
2977
+ parsed.response_format = responseFormat;
2978
+ const newInit = { ...init, body: JSON.stringify(parsed) };
2979
+ return ctx.fetch(input, newInit);
2980
+ }
2981
+ return ctx.fetch(input, init);
2982
+ })
2983
+ };
2984
+ const inner = streamInferenceFromContext(overrideCtx, textParams);
2985
+ return wrapStreamTextAsObject(inner);
2986
+ }
2987
+ function wrapStreamTextAsObject(inner) {
2988
+ const partialBroadcast = new PartialObjectBroadcast();
2989
+ const objectDeferred = defer();
2990
+ void (async () => {
2991
+ let acc = "";
2992
+ let lastEmitted = /* @__PURE__ */ Symbol("none");
2993
+ try {
2994
+ for await (const delta of inner.textStream) {
2995
+ acc += delta;
2996
+ const candidate = tryParsePartialJson(acc);
2997
+ if (candidate !== void 0 && !sameSnapshot(candidate, lastEmitted)) {
2998
+ lastEmitted = candidate;
2999
+ partialBroadcast.push(candidate);
3000
+ }
3001
+ }
3002
+ const finalText = await inner.text;
3003
+ let finalObj;
3004
+ try {
3005
+ finalObj = JSON.parse(finalText);
3006
+ } catch (e) {
3007
+ const err = new RagableError(
3008
+ `Model output is not valid JSON: ${e.message}`,
3009
+ 502,
3010
+ {
3011
+ code: "SDK_OBJECT_PARSE_FAILED",
3012
+ raw: finalText.slice(0, 1e3)
3013
+ }
3014
+ );
3015
+ partialBroadcast.fail(err);
3016
+ objectDeferred.reject(err);
3017
+ return;
3018
+ }
3019
+ if (!sameSnapshot(finalObj, lastEmitted)) {
3020
+ partialBroadcast.push(finalObj);
3021
+ }
3022
+ partialBroadcast.end();
3023
+ objectDeferred.resolve(finalObj);
3024
+ } catch (err) {
3025
+ partialBroadcast.fail(err);
3026
+ objectDeferred.reject(err);
3027
+ }
3028
+ })();
3029
+ return {
3030
+ textStream: inner.textStream,
3031
+ partialObjectStream: partialBroadcast.consume(),
3032
+ object: objectDeferred.promise,
3033
+ text: inner.text,
3034
+ usage: inner.usage,
3035
+ finishReason: inner.finishReason,
3036
+ toolCalls: inner.toolCalls
3037
+ };
3038
+ }
3039
+ function sameSnapshot(a, b) {
3040
+ if (a === b) return true;
3041
+ try {
3042
+ return JSON.stringify(a) === JSON.stringify(b);
3043
+ } catch {
3044
+ return false;
3045
+ }
3046
+ }
3047
+ var PartialObjectBroadcast = class {
3048
+ constructor() {
3049
+ __publicField(this, "items", []);
3050
+ __publicField(this, "resolved", false);
3051
+ __publicField(this, "error", null);
3052
+ __publicField(this, "waiters", []);
3053
+ }
3054
+ push(item) {
3055
+ this.items.push(item);
3056
+ this.notify();
3057
+ }
3058
+ end() {
3059
+ if (this.resolved) return;
3060
+ this.resolved = true;
3061
+ this.notify();
3062
+ }
3063
+ fail(error) {
3064
+ if (this.resolved) return;
3065
+ this.error = error;
3066
+ this.resolved = true;
3067
+ this.notify();
3068
+ }
3069
+ notify() {
3070
+ const w = this.waiters;
3071
+ this.waiters = [];
3072
+ for (const fn of w) fn();
3073
+ }
3074
+ consume() {
3075
+ const self = this;
3076
+ return {
3077
+ [Symbol.asyncIterator]: () => {
3078
+ let idx = 0;
3079
+ return {
3080
+ next: async () => {
3081
+ while (true) {
3082
+ if (idx < self.items.length) {
3083
+ return { value: self.items[idx++], done: false };
3084
+ }
3085
+ if (self.resolved) {
3086
+ if (self.error) throw self.error;
3087
+ return { value: void 0, done: true };
3088
+ }
3089
+ await new Promise((res) => self.waiters.push(res));
3090
+ }
3091
+ }
3092
+ };
3093
+ }
3094
+ };
3095
+ }
2832
3096
  };
2833
3097
 
2834
3098
  // src/browser.ts
@@ -3691,6 +3955,63 @@ var RagableBrowserAgentsClient = class {
3691
3955
  const source = this.runStreamParts(agentName, params);
3692
3956
  return createStreamResultFromParts(source);
3693
3957
  }
3958
+ /**
3959
+ * Same agent, same tools/instructions/RAG — but constrain the final output
3960
+ * to a JSON Schema. Matches `client.ai.streamObject` exactly so callers can
3961
+ * swap between raw inference and an agent without changing call shape.
3962
+ *
3963
+ * Tool calling and structured output are **compatible**: the model may call
3964
+ * the agent's tools as usual; the final assistant message is the schema-
3965
+ * conformant JSON. The agent's `agents/<name>.json` is unchanged — whether
3966
+ * the run is conversational or structured is decided by the call site.
3967
+ *
3968
+ * ```ts
3969
+ * const { partialObjectStream, object } = client.agents.runObject<Plan>(
3970
+ * "planner",
3971
+ * {
3972
+ * messages: [{ role: "user", content: "Plan a 3-day trip to Kyoto." }],
3973
+ * schema: {
3974
+ * type: "object",
3975
+ * properties: {
3976
+ * days: {
3977
+ * type: "array",
3978
+ * items: { type: "object", properties: { date: { type: "string" }, activities: { type: "array", items: { type: "string" } } } },
3979
+ * },
3980
+ * },
3981
+ * required: ["days"],
3982
+ * },
3983
+ * },
3984
+ * );
3985
+ * for await (const p of partialObjectStream) renderPreview(p);
3986
+ * console.log(await object);
3987
+ * ```
3988
+ */
3989
+ runObject(agentName, params) {
3990
+ const responseFormat = buildResponseFormat({
3991
+ schema: params.schema,
3992
+ ...params.schemaName !== void 0 ? { name: params.schemaName } : {},
3993
+ ...params.schemaDescription !== void 0 ? { description: params.schemaDescription } : {}
3994
+ });
3995
+ const sourceParts = this.runStreamParts(agentName, {
3996
+ messages: params.messages,
3997
+ ...params.signal !== void 0 ? { signal: params.signal } : {},
3998
+ responseFormat
3999
+ });
4000
+ const inner = createStreamResultFromParts(sourceParts);
4001
+ return wrapStreamTextAsObject(inner);
4002
+ }
4003
+ /** Non-streaming variant of {@link runObject}. */
4004
+ async generateObject(agentName, params) {
4005
+ const result = this.runObject(agentName, params);
4006
+ for await (const _ of result.partialObjectStream) void _;
4007
+ const [object, usage, finishReason, toolCalls] = await Promise.all([
4008
+ result.object,
4009
+ result.usage,
4010
+ result.finishReason,
4011
+ result.toolCalls
4012
+ ]);
4013
+ return { object, usage, finishReason, toolCalls };
4014
+ }
3694
4015
  async *runStreamParts(agentName, params) {
3695
4016
  const { messages } = params;
3696
4017
  if (!Array.isArray(messages) || messages.length === 0) {
@@ -3712,12 +4033,33 @@ var RagableBrowserAgentsClient = class {
3712
4033
  role: m.role,
3713
4034
  content: m.content
3714
4035
  }));
3715
- const events = this.chatStreamByName(agentName, {
4036
+ const headers = new Headers(this.options.headers);
4037
+ headers.set("Content-Type", "application/json");
4038
+ const body = {
3716
4039
  message: last.content,
3717
4040
  ...history.length > 0 ? { history } : {},
3718
- ...params.signal !== void 0 ? { signal: params.signal } : {}
3719
- });
3720
- for await (const event of events) {
4041
+ ...params.responseFormat ? { response_format: params.responseFormat } : {}
4042
+ };
4043
+ const response = await this.fetchImpl(
4044
+ this.toUrl(
4045
+ this.websiteAgentPath(
4046
+ `/agents/${encodeURIComponent(agentName)}/chat/stream`
4047
+ )
4048
+ ),
4049
+ {
4050
+ method: "POST",
4051
+ headers,
4052
+ body: JSON.stringify(body),
4053
+ ...params.signal !== void 0 ? { signal: params.signal } : {}
4054
+ }
4055
+ );
4056
+ if (!response.ok) {
4057
+ const payload = await parseMaybeJsonBody(response);
4058
+ const message = extractErrorMessage(payload, response.statusText);
4059
+ throw new RagableError(message, response.status, payload);
4060
+ }
4061
+ if (!response.body) return;
4062
+ for await (const event of readSseStream(response.body)) {
3721
4063
  const mapped = mapAgentEvent(event);
3722
4064
  if (mapped) yield mapped;
3723
4065
  }
@@ -3955,6 +4297,7 @@ function createClient(options) {
3955
4297
  assertPostgrestSuccess,
3956
4298
  bindFetch,
3957
4299
  buildInferenceRequestBody,
4300
+ buildResponseFormat,
3958
4301
  collectAssistantTextFromUiSegments,
3959
4302
  collectionRecordToRowWithMeta,
3960
4303
  collectionRecordsToRowWithMeta,
@@ -3982,7 +4325,10 @@ function createClient(options) {
3982
4325
  runAgentChatStream,
3983
4326
  runAgentChatStreamForUi,
3984
4327
  runAgentChatStreamLenient,
4328
+ streamObjectFromContext,
3985
4329
  toRagableResult,
3986
- unwrapPostgrest
4330
+ tryParsePartialJson,
4331
+ unwrapPostgrest,
4332
+ wrapStreamTextAsObject
3987
4333
  });
3988
4334
  //# sourceMappingURL=index.js.map