@blazingagents/sdk 0.22.0 → 0.24.0

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.
@@ -5,6 +5,12 @@ export type BlazingAgentsChatTransportOptions<UI_MESSAGE extends UIMessage = UIM
5
5
  * @param sessionId - Session to look up or record.
6
6
  */
7
7
  onSessionId?: (sessionId: string) => Promise<void> | void;
8
+ /**
9
+ * Receives the running Turn ID from each response that names one, before
10
+ * its stream is consumed. Pass it to Stop.
11
+ * @param turnId - Turn the response runs.
12
+ */
13
+ onTurnId?: (turnId: string) => Promise<void> | void;
8
14
  /** An authorized Session ID used to resume after a remount or reload. */
9
15
  sessionId?: string;
10
16
  };
@@ -1,6 +1,6 @@
1
1
  import { lastAssistantMessageIsCompleteWithApprovalResponses, } from "ai";
2
2
  import { ChatStreamTransport } from "./chat-stream-transport.js";
3
- import { sessionIdSchema } from "./contracts/ids.js";
3
+ import { sessionIdSchema, turnIdSchema } from "./contracts/ids.js";
4
4
  /**
5
5
  * Adapts AI SDK chat requests to the Blazing Agents backend-relay shape and
6
6
  * resumes with the server-minted Session ID returned in `Location`.
@@ -13,27 +13,34 @@ export class BlazingAgentsChatTransport {
13
13
  * @param options - Configuration for this operation.
14
14
  */
15
15
  constructor(options = {}) {
16
- const { onSessionId, sessionId, ...transportOptions } = options;
16
+ const { onSessionId, onTurnId, sessionId, ...transportOptions } = options;
17
17
  const transportFetch = transportOptions.fetch ?? globalThis.fetch;
18
18
  this.#sessionId =
19
19
  sessionId === undefined ? undefined : sessionIdSchema.parse(sessionId);
20
20
  this.#transport = new ChatStreamTransport({
21
21
  ...transportOptions,
22
- /** Reads the first Session ID from Location before returning the response. */
22
+ /** Reads the first Session ID and each Turn ID before returning the response. */
23
23
  fetch: async (input, init) => {
24
24
  const response = await transportFetch(input, init);
25
- if (response.ok && this.#sessionId === undefined) {
26
- try {
25
+ if (!response.ok) {
26
+ return response;
27
+ }
28
+ try {
29
+ if (this.#sessionId === undefined) {
27
30
  const location = response.headers.get("location");
28
31
  const candidate = location?.split("/").pop();
29
32
  this.#sessionId = sessionIdSchema.parse(candidate);
30
33
  await onSessionId?.(this.#sessionId);
31
34
  }
32
- catch (error) {
33
- await response.body?.cancel(error).catch(() => undefined);
34
- throw error;
35
+ const turnId = turnIdSchema.safeParse(response.headers.get("x-ba-turn-id"));
36
+ if (turnId.success) {
37
+ await onTurnId?.(turnId.data);
35
38
  }
36
39
  }
40
+ catch (error) {
41
+ await response.body?.cancel(error).catch(() => undefined);
42
+ throw error;
43
+ }
37
44
  return response;
38
45
  },
39
46
  /** Selects the user message or complete approval round for the relay request. */
@@ -53,7 +53,6 @@ export declare const spendingLimitResponseSchema: z.ZodObject<{
53
53
  availableUsd: z.ZodNumber;
54
54
  }, z.core.$strict>>;
55
55
  nextResetAt: z.ZodNullable<z.ZodISODateTime>;
56
- scheduleChangeAt: z.ZodNullable<z.ZodISODateTime>;
57
56
  }, z.core.$strict>;
58
57
  export declare const spendingLimitStopDetailsSchema: z.ZodObject<{
59
58
  scope: z.ZodEnum<{
@@ -76,7 +75,6 @@ export type SpendingLimit = z.infer<typeof spendingLimitSchema>;
76
75
  export type SpendingLimitResponse = z.infer<typeof spendingLimitResponseSchema>;
77
76
  export type SpendingLimitStopDetails = z.infer<typeof spendingLimitStopDetailsSchema>;
78
77
  export type UpdateSpendingLimitBody = z.infer<typeof updateSpendingLimitBodySchema>;
79
- /** A schedule edit uses the active period end as `after` for its first new boundary. */
80
78
  export declare function nextSpendingLimitReset(schedule: Pick<SpendingLimit, "resetStartDate" | "resetInterval">, after: string | Date): string;
81
79
  export declare const spendingLimitStopEventSchema: z.ZodObject<{
82
80
  type: z.ZodLiteral<"data-model-spending-limit">;
@@ -34,7 +34,6 @@ export const spendingLimitResponseSchema = z
34
34
  spendingLimit: spendingLimitSchema.nullable(),
35
35
  period: spendingLimitPeriodSchema.nullable(),
36
36
  nextResetAt: z.iso.datetime({ offset: true }).nullable(),
37
- scheduleChangeAt: z.iso.datetime({ offset: true }).nullable(),
38
37
  })
39
38
  .strict()
40
39
  .meta({ id: "ModelSpendingLimitStatus" });
@@ -48,7 +47,6 @@ export const spendingLimitStopDetailsSchema = z
48
47
  nextResetAt: z.iso.datetime({ offset: true }).nullable(),
49
48
  })
50
49
  .strict();
51
- /** A schedule edit uses the active period end as `after` for its first new boundary. */
52
50
  export function nextSpendingLimitReset(schedule, after) {
53
51
  const anchor = new Date(`${schedule.resetStartDate}T00:00:00.000Z`);
54
52
  const instant = new Date(after).getTime();
@@ -17,6 +17,12 @@ export type BlazingAgentsDirectChatTransportOptions = {
17
17
  * @param sessionId - Session to look up or record.
18
18
  */
19
19
  onSessionId?: (sessionId: string) => Promise<void> | void;
20
+ /**
21
+ * Receives the running Turn ID before the response stream is consumed.
22
+ * Pass it to Stop.
23
+ * @param turnId - Turn the response runs.
24
+ */
25
+ onTurnId?: (turnId: string) => Promise<void> | void;
20
26
  sessionId?: string;
21
27
  userId?: string;
22
28
  } & ({
@@ -96,7 +96,8 @@ export class BlazingAgentsDirectChatTransport extends ChatStreamTransport {
96
96
  return this.#processChatResult(result);
97
97
  }
98
98
  /**
99
- * Claims the chat stream and records the first Session ID before decoding chunks.
99
+ * Claims the chat stream and records the first Session ID and the Turn ID
100
+ * before decoding chunks.
100
101
  */
101
102
  async #processChatResult(result) {
102
103
  const stream = result.toStream();
@@ -106,6 +107,7 @@ export class BlazingAgentsDirectChatTransport extends ChatStreamTransport {
106
107
  this.#sessionId = sessionId;
107
108
  await this.#options.onSessionId?.(sessionId);
108
109
  }
110
+ await this.#reportTurnId(result);
109
111
  }
110
112
  catch (error) {
111
113
  await stream.cancel(error).catch(() => undefined);
@@ -141,6 +143,21 @@ export class BlazingAgentsDirectChatTransport extends ChatStreamTransport {
141
143
  decisions,
142
144
  functions: this.#options.functions ?? {},
143
145
  });
144
- return this.processResponseStream(continuation.toStream());
146
+ const stream = continuation.toStream();
147
+ try {
148
+ await this.#reportTurnId(continuation);
149
+ }
150
+ catch (error) {
151
+ await stream.cancel(error).catch(() => undefined);
152
+ throw error;
153
+ }
154
+ return this.processResponseStream(stream);
155
+ }
156
+ /** Reports the Turn ID when the response names one. */
157
+ async #reportTurnId(result) {
158
+ const turnId = await result.turnId.catch(() => undefined);
159
+ if (turnId !== undefined) {
160
+ await this.#options.onTurnId?.(turnId);
161
+ }
145
162
  }
146
163
  }
@@ -1,5 +1,5 @@
1
1
  import { createTextStreamResponse, parsePartialJson } from "ai";
2
- import { sessionIdSchema } from "./contracts/ids.js";
2
+ import { sessionIdSchema, turnIdSchema } from "./contracts/ids.js";
3
3
  import { BlazingAgentsError } from "./errors.js";
4
4
  import { dispatchChatFunctions, toChatFunctionDefinitions, } from "./functions.js";
5
5
  import { requestStream } from "./http.js";
@@ -165,12 +165,24 @@ function buildChatResult(config, response, resumeSessionId, dispatch) {
165
165
  sessionIdPromise.catch(() => {
166
166
  /* no-op — prevents unhandled rejection if the caller never awaits */
167
167
  });
168
+ const turnId = turnIdSchema.safeParse(response.headers.get("x-ba-turn-id"));
169
+ const turnIdPromise = turnId.success
170
+ ? Promise.resolve(turnId.data)
171
+ : Promise.reject(new BlazingAgentsError({
172
+ code: "stream_error",
173
+ message: "The server did not return a valid turn id (X-BA-Turn-Id header).",
174
+ requestId: response.headers.get("x-request-id") ?? undefined,
175
+ }, { cause: turnId.error }));
176
+ turnIdPromise.catch(() => {
177
+ /* no-op — prevents unhandled rejection if the caller never awaits */
178
+ });
168
179
  const { functions } = dispatch;
169
180
  const streamResponse = functions && sessionId !== undefined
170
181
  ? withFilteredBody(response, (body) => dispatchChatFunctions(config, { ...dispatch, functions, sessionId }, body))
171
182
  : response;
172
183
  return {
173
184
  sessionId: sessionIdPromise,
185
+ turnId: turnIdPromise,
174
186
  ...buildTerminalStreamResult(streamResponse, "chat"),
175
187
  };
176
188
  }
@@ -183,6 +195,7 @@ function buildChatResult(config, response, resumeSessionId, dispatch) {
183
195
  export function buildTerminalStreamResult(response, resourceName, filter) {
184
196
  const requestId = response.headers.get("x-request-id") ?? undefined;
185
197
  const location = response.headers.get("location");
198
+ const turnId = response.headers.get("x-ba-turn-id");
186
199
  let bodyClaimed = false;
187
200
  /**
188
201
  * Claims the response body once and normalizes stream failures.
@@ -207,7 +220,7 @@ export function buildTerminalStreamResult(response, resourceName, filter) {
207
220
  toStream: claimBody,
208
221
  /** Claims the response relay once. */
209
222
  toResponse: () => {
210
- const headers = replacementResponseHeaders(requestId, location);
223
+ const headers = replacementResponseHeaders(requestId, location, turnId);
211
224
  headers.set("content-type", "text/event-stream");
212
225
  headers.set("cache-control", "no-cache");
213
226
  headers.set("connection", "keep-alive");
@@ -318,7 +331,7 @@ function responseBodyStream(response, requestId, resourceName) {
318
331
  /**
319
332
  * Copies request correlation and Session location into relay response headers.
320
333
  */
321
- function replacementResponseHeaders(requestId, location) {
334
+ function replacementResponseHeaders(requestId, location, turnId) {
322
335
  const headers = new Headers();
323
336
  if (requestId !== undefined) {
324
337
  headers.set("x-request-id", requestId);
@@ -326,6 +339,9 @@ function replacementResponseHeaders(requestId, location) {
326
339
  if (location !== null) {
327
340
  headers.set("location", location);
328
341
  }
342
+ if (turnId !== null) {
343
+ headers.set("x-ba-turn-id", turnId);
344
+ }
329
345
  return headers;
330
346
  }
331
347
  /**
@@ -501,7 +517,7 @@ function buildStatelessGenerationStreams(response, resourceName) {
501
517
  }, { cause: new Error(message) });
502
518
  }
503
519
  responseBodyClaimed = true;
504
- const headers = replacementResponseHeaders(requestId, location);
520
+ const headers = replacementResponseHeaders(requestId, location, null);
505
521
  headers.set("content-type", "text/plain; charset=utf-8");
506
522
  try {
507
523
  return createTextStreamResponse({
package/dist/types.d.ts CHANGED
@@ -200,6 +200,12 @@ export interface ChatResult {
200
200
  * @throws BlazingAgentsError - If the response body has already been claimed.
201
201
  */
202
202
  toStream: () => ReadableStream<Uint8Array>;
203
+ /**
204
+ * Resolves to the id of the Turn this response runs, read from the
205
+ * `X-BA-Turn-Id` header. Pass it to `sessions.stop()` to stop the Turn.
206
+ * Rejects with `stream_error` when the header is missing or malformed.
207
+ */
208
+ turnId: Promise<string>;
203
209
  }
204
210
  export interface TerminalStreamResult {
205
211
  requestId?: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blazingagents/sdk",
3
- "version": "0.22.0",
3
+ "version": "0.24.0",
4
4
  "description": "TypeScript SDK for the Blazing Agents API",
5
5
  "homepage": "https://docs.blazingagents.com",
6
6
  "bugs": {