@blazingagents/sdk 0.15.0 → 0.17.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.
Files changed (43) hide show
  1. package/dist/approvals.d.ts +15 -0
  2. package/dist/approvals.js +20 -0
  3. package/dist/chat-transport.js +9 -2
  4. package/dist/client.d.ts +6 -1
  5. package/dist/client.js +9 -1
  6. package/dist/contracts/api.d.ts +3 -3
  7. package/dist/contracts/api.js +1 -1
  8. package/dist/contracts/entities/agent-approval.d.ts +32 -0
  9. package/dist/contracts/entities/agent-approval.js +12 -1
  10. package/dist/contracts/entities/agent-tools.d.ts +6 -0
  11. package/dist/contracts/entities/agent-tools.js +13 -0
  12. package/dist/contracts/entities/agents.d.ts +2 -118
  13. package/dist/contracts/entities/agents.js +2 -24
  14. package/dist/contracts/entities/chat.d.ts +95 -24
  15. package/dist/contracts/entities/chat.js +105 -39
  16. package/dist/contracts/entities/providers.d.ts +2 -6
  17. package/dist/contracts/entities/providers.js +2 -9
  18. package/dist/contracts/entities/sessions.d.ts +112 -5
  19. package/dist/contracts/entities/sessions.js +7 -5
  20. package/dist/contracts/entities/tasks.d.ts +285 -10
  21. package/dist/contracts/entities/tasks.js +2 -5
  22. package/dist/contracts/ids.d.ts +1 -0
  23. package/dist/contracts/ids.js +1 -0
  24. package/dist/contracts/index.d.ts +8 -7
  25. package/dist/contracts/index.js +6 -5
  26. package/dist/contracts/limitations.d.ts +0 -2
  27. package/dist/contracts/limitations.js +0 -2
  28. package/dist/direct-chat-transport.d.ts +3 -1
  29. package/dist/direct-chat-transport.js +20 -23
  30. package/dist/functions.d.ts +38 -0
  31. package/dist/functions.js +299 -0
  32. package/dist/generation.d.ts +7 -1
  33. package/dist/generation.js +64 -20
  34. package/dist/index.d.ts +7 -4
  35. package/dist/index.js +3 -0
  36. package/dist/relay.d.ts +11 -5
  37. package/dist/relay.js +41 -3
  38. package/dist/resources/agents.js +1 -33
  39. package/dist/resources/providers.js +1 -1
  40. package/dist/resources/sessions.d.ts +1 -1
  41. package/dist/resources/sessions.js +9 -4
  42. package/dist/types.d.ts +22 -23
  43. package/package.json +1 -1
@@ -0,0 +1,38 @@
1
+ import { z } from "zod";
2
+ import { type ChatFunctionCallEvent, type ChatFunctionDefinitions } from "./contracts/entities/chat.ts";
3
+ import type { HttpConfig } from "./types.ts";
4
+ export interface ChatFunctionContext {
5
+ /** The function call ID; a new user resend produces a new key. */
6
+ idempotencyKey: string;
7
+ /** Aborts at the call deadline or when the chat stream ends. */
8
+ signal: AbortSignal;
9
+ }
10
+ export interface ChatFunction<Schema extends z.ZodType = z.ZodType, Output = unknown> {
11
+ description: string;
12
+ execute(input: z.output<Schema>, context: ChatFunctionContext): Output | Promise<Output>;
13
+ inputSchema: Schema;
14
+ }
15
+ export type ChatFunctions = Record<string, ChatFunction>;
16
+ /** Declares a caller-local function whose handler input is inferred from its Zod schema. */
17
+ export declare function defineFunction<Schema extends z.ZodType, Output>(definition: ChatFunction<Schema, Output>): ChatFunction<Schema, Output>;
18
+ export declare function toChatFunctionDefinitions(functions: ChatFunctions): ChatFunctionDefinitions;
19
+ export interface FunctionDispatchTarget {
20
+ abortSignal?: AbortSignal;
21
+ agentId: string;
22
+ functions: ChatFunctions;
23
+ sessionId: string;
24
+ }
25
+ interface FunctionEventHandlers {
26
+ onCall?(call: ChatFunctionCallEvent["data"], fail: (error: unknown) => void): void;
27
+ onCancel?(): void;
28
+ onEnd?(): void;
29
+ }
30
+ /**
31
+ * Reads native SSE eagerly and returns it without private function events,
32
+ * so slow consumers never delay a call. A recognized but malformed event
33
+ * fails the stream.
34
+ */
35
+ export declare function stripFunctionEvents(body: ReadableStream<Uint8Array>, { onCall, onCancel, onEnd }?: FunctionEventHandlers): ReadableStream<Uint8Array>;
36
+ /** Strips private function events and executes claimed calls without blocking the read loop. */
37
+ export declare function dispatchChatFunctions(config: HttpConfig, target: FunctionDispatchTarget, body: ReadableStream<Uint8Array>): ReadableStream<Uint8Array>;
38
+ export {};
@@ -0,0 +1,299 @@
1
+ import { z } from "zod";
2
+ import { chatFunctionCallEventSchema, chatFunctionDefinitionsSchema, chatFunctionOutcomeSchema, claimChatFunctionResponseSchema, resolveChatFunctionResponseSchema, } from "./contracts/entities/chat.js";
3
+ import { BlazingAgentsError } from "./errors.js";
4
+ import { requestJson } from "./http.js";
5
+ /** Declares a caller-local function whose handler input is inferred from its Zod schema. */
6
+ export function defineFunction(definition) {
7
+ return definition;
8
+ }
9
+ export function toChatFunctionDefinitions(functions) {
10
+ try {
11
+ return chatFunctionDefinitionsSchema.parse(Object.fromEntries(Object.entries(functions).map(([name, fn]) => {
12
+ const { $schema: _, ...inputSchema } = z.toJSONSchema(fn.inputSchema, { io: "input" });
13
+ return [name, { description: fn.description, inputSchema }];
14
+ })));
15
+ }
16
+ catch (cause) {
17
+ throw new BlazingAgentsError({
18
+ code: "invalid_request",
19
+ message: `Invalid chat functions: ${cause instanceof z.ZodError ? z.prettifyError(cause) : String(cause)}`,
20
+ }, { cause });
21
+ }
22
+ }
23
+ const EVENT_BOUNDARY = /\r\n\r\n|\n\n/;
24
+ const LINE_BREAK = /\r?\n/;
25
+ const DELAY_SECONDS = /^\d+$/;
26
+ const FUNCTION_CALL_TYPE = "data-ba-function-call";
27
+ const RETRY_BASE_MS = 250;
28
+ const RETRY_MAX_MS = 2000;
29
+ /** Results may retry past the deadline to recover an already accepted receipt. */
30
+ const RESULT_RETRY_GRACE_MS = 30_000;
31
+ /**
32
+ * `undefined` forwards the SSE block unchanged; `null` marks a recognized
33
+ * but malformed function event.
34
+ */
35
+ function functionCallFrom(block) {
36
+ const data = block
37
+ .split(LINE_BREAK)
38
+ .filter((line) => line.startsWith("data:"))
39
+ .map((line) => line.slice(line.startsWith("data: ") ? 6 : 5))
40
+ .join("\n");
41
+ if (!data.includes(FUNCTION_CALL_TYPE)) {
42
+ return;
43
+ }
44
+ let parsed;
45
+ try {
46
+ parsed = JSON.parse(data);
47
+ }
48
+ catch {
49
+ /** Unparseable frames naming the private event never reach the browser. */
50
+ return null;
51
+ }
52
+ if (parsed?.type !== FUNCTION_CALL_TYPE) {
53
+ return;
54
+ }
55
+ return chatFunctionCallEventSchema.safeParse(parsed).data ?? null;
56
+ }
57
+ function retryAfterMs(headers) {
58
+ const value = headers?.get("retry-after");
59
+ if (!value) {
60
+ return;
61
+ }
62
+ const ms = DELAY_SECONDS.test(value)
63
+ ? Number(value) * 1000
64
+ : Date.parse(value) - Date.now();
65
+ return Number.isNaN(ms) ? undefined : Math.max(0, ms);
66
+ }
67
+ function retryDelayMs({ code, headers, status }, attempt) {
68
+ const retryable = status === undefined
69
+ ? code === "network_error"
70
+ : status === 408 || status === 429 || status >= 500;
71
+ if (!retryable) {
72
+ return;
73
+ }
74
+ return (retryAfterMs(headers) ??
75
+ Math.min(RETRY_BASE_MS * 2 ** (attempt - 1), RETRY_MAX_MS));
76
+ }
77
+ function sleep(ms, signal) {
78
+ return new Promise((resolve) => {
79
+ const timer = setTimeout(resolve, ms);
80
+ signal.addEventListener("abort", () => {
81
+ clearTimeout(timer);
82
+ resolve();
83
+ }, { once: true });
84
+ });
85
+ }
86
+ /**
87
+ * POSTs one protocol request, retrying transient failures with the same
88
+ * body until `until` or abort. A 409 is a lost or stale race; other
89
+ * permanent failures throw.
90
+ */
91
+ async function postWithRetry(config, path, json, schema, until, signal) {
92
+ for (let attempt = 1;; attempt += 1) {
93
+ const remaining = until - Date.now();
94
+ if (signal.aborted || remaining <= 0) {
95
+ return "expired";
96
+ }
97
+ try {
98
+ await requestJson(config, path, {
99
+ json,
100
+ method: "POST",
101
+ signal: AbortSignal.any([signal, AbortSignal.timeout(remaining)]),
102
+ }, schema);
103
+ return "accepted";
104
+ }
105
+ catch (caught) {
106
+ const next = afterFailure(caught, attempt, until, signal);
107
+ if (typeof next === "string") {
108
+ return next;
109
+ }
110
+ await sleep(next, signal);
111
+ }
112
+ }
113
+ }
114
+ /** Classifies a failed attempt as a lost race, an exhausted budget, or the delay before retrying; permanent failures rethrow. */
115
+ function afterFailure(caught, attempt, until, signal) {
116
+ const error = BlazingAgentsError.isInstance(caught) ? caught : undefined;
117
+ if (error?.status === 409) {
118
+ return "conflict";
119
+ }
120
+ if (signal.aborted || Date.now() >= until) {
121
+ return "expired";
122
+ }
123
+ const delay = error && retryDelayMs(error, attempt);
124
+ if (delay === undefined) {
125
+ throw caught;
126
+ }
127
+ return Date.now() + delay >= until ? "expired" : delay;
128
+ }
129
+ const invalidResult = {
130
+ kind: "error",
131
+ message: "Function returned an invalid result.",
132
+ };
133
+ /** Resolves `undefined` when the call became late or aborted before customer code could start. */
134
+ async function execute(functions, { id, name, input }, deadline, signal) {
135
+ const fn = Object.hasOwn(functions, name) ? functions[name] : undefined;
136
+ if (fn === undefined) {
137
+ return { kind: "error", message: `Function ${name} is not available.` };
138
+ }
139
+ /** Refinements and transforms are customer code too. */
140
+ if (signal.aborted || Date.now() >= deadline) {
141
+ return;
142
+ }
143
+ let parsed;
144
+ try {
145
+ parsed = await fn.inputSchema.safeParseAsync(input);
146
+ }
147
+ catch {
148
+ /** Zod rethrows errors thrown inside refinements and transforms. */
149
+ return { kind: "error", message: "Invalid function input." };
150
+ }
151
+ if (!parsed.success) {
152
+ return { kind: "error", message: "Invalid function input." };
153
+ }
154
+ if (signal.aborted || Date.now() >= deadline) {
155
+ return;
156
+ }
157
+ let value;
158
+ try {
159
+ value = await fn.execute(parsed.data, { idempotencyKey: id, signal });
160
+ }
161
+ catch {
162
+ return { kind: "error", message: "Function execution failed." };
163
+ }
164
+ try {
165
+ /** Only plain JSON is accepted; undefined, NaN, Dates and class instances are invalid. */
166
+ return (chatFunctionOutcomeSchema.safeParse({ kind: "output", value }).data ??
167
+ invalidResult);
168
+ }
169
+ catch {
170
+ /** Cyclic values fail serialization in the size check. */
171
+ return invalidResult;
172
+ }
173
+ }
174
+ /**
175
+ * Reads native SSE eagerly and returns it without private function events,
176
+ * so slow consumers never delay a call. A recognized but malformed event
177
+ * fails the stream.
178
+ */
179
+ export function stripFunctionEvents(body, { onCall, onCancel, onEnd } = {}) {
180
+ const reader = body.getReader();
181
+ const decoder = new TextDecoder();
182
+ const encoder = new TextEncoder();
183
+ let output;
184
+ let finished = false;
185
+ const fail = (error) => {
186
+ if (finished) {
187
+ return;
188
+ }
189
+ finished = true;
190
+ output.error(error);
191
+ reader.cancel(error).catch(() => undefined);
192
+ };
193
+ const handleBlock = (block, raw) => {
194
+ const call = functionCallFrom(block);
195
+ if (call === undefined) {
196
+ if (raw && !finished) {
197
+ output.enqueue(encoder.encode(raw));
198
+ }
199
+ }
200
+ else if (call === null) {
201
+ fail(new BlazingAgentsError({
202
+ code: "stream_error",
203
+ message: "The server sent a malformed function call event.",
204
+ }));
205
+ }
206
+ else {
207
+ onCall?.(call.data, fail);
208
+ }
209
+ };
210
+ const pump = async () => {
211
+ let buffer = "";
212
+ try {
213
+ for (;;) {
214
+ const { done, value } = await reader.read();
215
+ if (done) {
216
+ break;
217
+ }
218
+ buffer += decoder.decode(value, { stream: true });
219
+ for (let match = EVENT_BOUNDARY.exec(buffer); match; match = EVENT_BOUNDARY.exec(buffer)) {
220
+ const end = match.index + match[0].length;
221
+ handleBlock(buffer.slice(0, match.index), buffer.slice(0, end));
222
+ buffer = buffer.slice(end);
223
+ }
224
+ }
225
+ handleBlock("", buffer + decoder.decode());
226
+ if (!finished) {
227
+ finished = true;
228
+ output.close();
229
+ }
230
+ }
231
+ catch (error) {
232
+ fail(error);
233
+ }
234
+ finally {
235
+ onEnd?.();
236
+ }
237
+ };
238
+ return new ReadableStream({
239
+ start(controller) {
240
+ output = controller;
241
+ pump();
242
+ },
243
+ async cancel(reason) {
244
+ finished = true;
245
+ onCancel?.();
246
+ await reader.cancel(reason);
247
+ },
248
+ });
249
+ }
250
+ /** Strips private function events and executes claimed calls without blocking the read loop. */
251
+ export function dispatchChatFunctions(config, target, body) {
252
+ /** Caller abort or consumer cancellation: stops every retry. */
253
+ const stop = new AbortController();
254
+ /** Also aborted when the stream ends: stops claims and handlers. */
255
+ const handlers = new AbortController();
256
+ const abort = () => stop.abort();
257
+ stop.signal.addEventListener("abort", () => handlers.abort(), {
258
+ once: true,
259
+ });
260
+ target.abortSignal?.addEventListener("abort", abort, { once: true });
261
+ if (target.abortSignal?.aborted) {
262
+ abort();
263
+ }
264
+ const dispatched = new Set();
265
+ const runCall = async (call) => {
266
+ const path = `/v1/agents/${target.agentId}/sessions/${target.sessionId}/function-calls/${call.id}`;
267
+ const claimRequestId = crypto.randomUUID();
268
+ const deadline = Date.parse(call.deadlineAt);
269
+ const claim = await postWithRetry(config, `${path}/claim`, { claimRequestId }, claimChatFunctionResponseSchema, deadline, handlers.signal);
270
+ if (claim !== "accepted") {
271
+ return;
272
+ }
273
+ /**
274
+ * A grant can arrive just before the deadline and be processed after it,
275
+ * or after abort; `execute` rechecks immediately before customer code.
276
+ */
277
+ const outcome = await execute(target.functions, call, deadline, AbortSignal.any([
278
+ handlers.signal,
279
+ AbortSignal.timeout(Math.max(0, deadline - Date.now())),
280
+ ]));
281
+ if (outcome === undefined) {
282
+ return;
283
+ }
284
+ await postWithRetry(config, `${path}/result`, { claimRequestId, outcome }, resolveChatFunctionResponseSchema, deadline + RESULT_RETRY_GRACE_MS, stop.signal);
285
+ };
286
+ return stripFunctionEvents(body, {
287
+ onCall(call, fail) {
288
+ if (!dispatched.has(call.id)) {
289
+ dispatched.add(call.id);
290
+ runCall(call).catch(fail);
291
+ }
292
+ },
293
+ onCancel: abort,
294
+ onEnd() {
295
+ handlers.abort();
296
+ target.abortSignal?.removeEventListener("abort", abort);
297
+ },
298
+ });
299
+ }
@@ -1,5 +1,11 @@
1
- import type { ChatInput, ChatResult, CompletionInput, CompletionResult, HttpConfig, ObjectInput, ObjectResult, TerminalStreamResult } from "./types.ts";
1
+ import type { ChatInput, ChatResult, CompletionInput, CompletionResult, HttpConfig, ObjectInput, ObjectResult, ResumeChatInput, TerminalStreamResult } from "./types.ts";
2
2
  export declare function chat(config: HttpConfig, input: ChatInput): Promise<ChatResult>;
3
+ export declare function resumeChat(config: HttpConfig, input: ResumeChatInput): Promise<ChatResult>;
4
+ /**
5
+ * Keeps status and headers while filtering the SSE body. Missing or locked
6
+ * bodies pass through so the stream result reports them as `stream_error`.
7
+ */
8
+ export declare function withFilteredBody(response: Response, filter: (body: ReadableStream<Uint8Array>) => ReadableStream<Uint8Array>): Response;
3
9
  export declare function buildTerminalStreamResult(response: Response, resourceName: string): TerminalStreamResult;
4
10
  export declare function completion(config: HttpConfig, input: CompletionInput): Promise<CompletionResult>;
5
11
  export declare function objectGeneration(config: HttpConfig, input: ObjectInput): Promise<ObjectResult>;
@@ -1,9 +1,14 @@
1
1
  import { createTextStreamResponse, parsePartialJson } from "ai";
2
+ import { toolApprovalsResponseSchema } from "./contracts/entities/sessions.js";
2
3
  import { sessionIdSchema } from "./contracts/ids.js";
3
4
  import { BlazingAgentsError } from "./errors.js";
4
- import { requestStream } from "./http.js";
5
+ import { dispatchChatFunctions, toChatFunctionDefinitions, } from "./functions.js";
6
+ import { requestJson, requestStream } from "./http.js";
5
7
  export async function chat(config, input) {
6
8
  const body = buildChatBody(input);
9
+ if (input.functions && Object.keys(input.functions).length > 0) {
10
+ body.functions = toChatFunctionDefinitions(input.functions);
11
+ }
7
12
  /**
8
13
  * URL presence is the mode: no `sessionId` → create
9
14
  * (`POST /v1/agents/:agentId/sessions`, server mints the `ss_` id,
@@ -19,7 +24,50 @@ export async function chat(config, input) {
19
24
  clientRequestId: input.clientRequestId,
20
25
  ...(input.abortSignal ? { signal: input.abortSignal } : {}),
21
26
  });
22
- return buildChatResult(response, input.sessionId);
27
+ return buildChatResult(config, response, input.sessionId, {
28
+ agentId: input.agentId,
29
+ functions: input.functions,
30
+ abortSignal: input.abortSignal,
31
+ });
32
+ }
33
+ export async function resumeChat(config, input) {
34
+ const sessionPath = `/v1/agents/${input.agentId}/sessions/${input.sessionId}`;
35
+ const continuationId = input.continuationId ?? (await activeContinuationId(config, input));
36
+ const response = await requestStream(config, `${sessionPath}/tool-approval-continuations/${continuationId}/resume`, {
37
+ json: {},
38
+ method: "POST",
39
+ clientRequestId: input.clientRequestId,
40
+ ...(input.abortSignal ? { signal: input.abortSignal } : {}),
41
+ });
42
+ return buildChatResult(config, response, input.sessionId, {
43
+ agentId: input.agentId,
44
+ functions: input.functions,
45
+ abortSignal: input.abortSignal,
46
+ });
47
+ }
48
+ async function activeContinuationId(config, { agentId, sessionId, abortSignal }) {
49
+ const { continuation } = await requestJson(config, `/v1/agents/${agentId}/sessions/${sessionId}/tool-approvals`, { signal: abortSignal }, toolApprovalsResponseSchema);
50
+ if (continuation?.state !== "queued" && continuation?.state !== "running") {
51
+ throw new BlazingAgentsError({
52
+ code: "not_found",
53
+ message: "The Session has no tool approval continuation to resume.",
54
+ });
55
+ }
56
+ return continuation.id;
57
+ }
58
+ /**
59
+ * Keeps status and headers while filtering the SSE body. Missing or locked
60
+ * bodies pass through so the stream result reports them as `stream_error`.
61
+ */
62
+ export function withFilteredBody(response, filter) {
63
+ if (!response.body || response.body.locked) {
64
+ return response;
65
+ }
66
+ return new Response(filter(response.body), {
67
+ headers: response.headers,
68
+ status: response.status,
69
+ statusText: response.statusText,
70
+ });
23
71
  }
24
72
  function buildChatBody(input) {
25
73
  const base = {};
@@ -31,10 +79,6 @@ function buildChatBody(input) {
31
79
  if (input.messageId !== undefined) {
32
80
  base.messageId = input.messageId;
33
81
  }
34
- // Stryker disable next-line ConditionalExpression: JSON serialization omits an undefined Version Pin.
35
- if (input.version !== undefined) {
36
- base.version = input.version;
37
- }
38
82
  if ("message" in input) {
39
83
  base.message = input.message;
40
84
  }
@@ -83,32 +127,36 @@ function sessionIdFromLocation(response, requestId) {
83
127
  }
84
128
  return parsed.data;
85
129
  }
86
- function buildChatResult(response, resumeSessionId) {
87
- const terminalResult = buildTerminalStreamResult(response, "chat");
130
+ function buildChatResult(config, response, resumeSessionId, dispatch) {
88
131
  /**
89
132
  * `sessionId` resolves from the `Location` header on create, or to the
90
133
  * passed id on resume. Read eagerly (the header is available before the
91
134
  * body streams) so awaiting `result.sessionId` does not depend on
92
135
  * draining the stream.
93
136
  */
94
- let sessionIdPromise;
95
- if (resumeSessionId === undefined) {
137
+ let sessionId = resumeSessionId;
138
+ let sessionIdError;
139
+ if (sessionId === undefined) {
96
140
  try {
97
- sessionIdPromise = Promise.resolve(sessionIdFromLocation(response, terminalResult.requestId));
141
+ sessionId = sessionIdFromLocation(response, response.headers.get("x-request-id") ?? undefined);
98
142
  }
99
143
  catch (error) {
100
- sessionIdPromise = Promise.reject(error);
144
+ sessionIdError = error;
101
145
  }
102
146
  }
103
- else {
104
- sessionIdPromise = Promise.resolve(resumeSessionId);
105
- }
147
+ const sessionIdPromise = sessionId === undefined
148
+ ? Promise.reject(sessionIdError)
149
+ : Promise.resolve(sessionId);
106
150
  sessionIdPromise.catch(() => {
107
151
  /* no-op — prevents unhandled rejection if the caller never awaits */
108
152
  });
153
+ const { functions } = dispatch;
154
+ const streamResponse = functions && sessionId !== undefined
155
+ ? withFilteredBody(response, (body) => dispatchChatFunctions(config, { ...dispatch, functions, sessionId }, body))
156
+ : response;
109
157
  return {
110
158
  sessionId: sessionIdPromise,
111
- ...terminalResult,
159
+ ...buildTerminalStreamResult(streamResponse, "chat"),
112
160
  };
113
161
  }
114
162
  export function buildTerminalStreamResult(response, resourceName) {
@@ -234,10 +282,6 @@ function replacementResponseHeaders(requestId, location) {
234
282
  }
235
283
  function buildStatelessGenerationBody(input, output) {
236
284
  const body = { output };
237
- // Stryker disable next-line ConditionalExpression: JSON serialization omits an undefined Version Pin.
238
- if (input.version !== undefined) {
239
- body.version = input.version;
240
- }
241
285
  if ("prompt" in input) {
242
286
  body.prompt = input.prompt;
243
287
  }
package/dist/index.d.ts CHANGED
@@ -6,19 +6,22 @@
6
6
  export type { UIMessage } from "ai";
7
7
  export { BlazingAgentsChatTransport, type BlazingAgentsChatTransportOptions, } from "./chat-transport.ts";
8
8
  export { BlazingAgents } from "./client.ts";
9
- export type { ApprovalDecision, ApprovalPolicy, ToolReference, } from "./contracts/entities/agent-approval.ts";
10
- export type { Agent, AgentResponse, AgentsListQuery, AgentsResponse, AgentVersion, AgentVersionsListQuery, AgentVersionsResponse, CreateAgentBody, UpdateAgentBody, } from "./contracts/entities/agents.ts";
9
+ export type { ApprovalDecision, ApprovalPolicy, ToolExecutionReference, ToolReference, } from "./contracts/entities/agent-approval.ts";
10
+ export type { Agent, AgentConfig, AgentResponse, AgentsListQuery, AgentsResponse, CreateAgentBody, UpdateAgentBody, } from "./contracts/entities/agents.ts";
11
+ export { agentConfigSchema } from "./contracts/entities/agents.ts";
11
12
  export type { ArtifactDownloadUrlResponse, ArtifactListItem, ArtifactsListResponse, } from "./contracts/entities/artifacts.ts";
12
13
  export type { ChatConfiguration, ChatConnection, ChatConnectionsResponse, ChatCredentials, ChatDeliveriesResponse, ChatDelivery, ChatDeliveryListStatus, ChatDeliveryStatus, ChatHealth, ChatIdentity, CreateChatConnectionBody, RotateChatConnectionBody, TenantChatDelivery, UpdateChatConnectionBody, } from "./contracts/entities/chat-connections.ts";
13
14
  export type { CreateMcpConnectionBody, McpAttachmentResponse, McpAttachmentsResponse, McpConnectionAuthType, McpConnectionOauthConnectResponse, McpConnectionReconnectResult, McpConnectionResponse, McpConnectionStatus, McpConnectionTestResponse, ReconnectMcpConnectionBody, UpdateMcpAttachmentBody, UpdateMcpConnectionBody, } from "./contracts/entities/mcp-connections.ts";
14
15
  export type { CreateMerchantConnectionBody, MerchantBindingResponse, MerchantBindingsResponse, MerchantConnection, MerchantConnectionResponse, MerchantCustomerBinding, MerchantGuard, MerchantProviderKind, MerchantUsageEvent, MerchantUsageEventResponse, MerchantUsageEventStatus, MerchantUsageEventsResponse, MerchantUsageSummary, MerchantUsageSummaryResponse, UpdateMerchantConnectionBody, UpsertMerchantBindingBody, } from "./contracts/entities/merchant.ts";
15
16
  export type { CreatePromptBody, PromptResponse, PromptsListQuery, PromptsResponse, UpdatePromptBody, } from "./contracts/entities/prompts.ts";
16
17
  export type { CreateProviderBody, ProviderListItem, ProviderModel, ProviderModelsResponse, ProviderResponse, ProvidersResponse, ProviderType, ThinkingLevelsResponse, UpdateProviderBody, } from "./contracts/entities/providers.ts";
17
- export type { ToolApprovalDecisionResponse, ToolApprovalState, ToolApprovalsResponse, } from "./contracts/entities/sessions.ts";
18
+ export type { SessionResponse, ToolApprovalDecisionResponse, ToolApprovalState, ToolApprovalsResponse, } from "./contracts/entities/sessions.ts";
19
+ export { sessionResponseSchema } from "./contracts/entities/sessions.ts";
18
20
  export type { CreateSkillBody, Skill, SkillArchiveType, SkillCopyResult, SkillCopyResults, SkillDetail, SkillFile, SkillsListResponse, } from "./contracts/entities/skills.ts";
19
21
  export type { SessionUsageQuery, SessionUsageResponse, UsageBucket, UsageOverviewQuery, UsageOverviewResponse, UsageTotals, } from "./contracts/entities/usage.ts";
20
22
  export type { CreateWorkspaceBody, UpdateWorkspaceBody, Workspace, WorkspaceNetworkPolicy, WorkspacesListResponse, } from "./contracts/entities/workspaces.ts";
21
23
  export { BlazingAgentsDirectChatTransport, type BlazingAgentsDirectChatTransportOptions, } from "./direct-chat-transport.ts";
22
24
  export { BlazingAgentsError } from "./errors.ts";
25
+ export { type ChatFunction, type ChatFunctionContext, type ChatFunctions, defineFunction, } from "./functions.ts";
23
26
  export { createChatRelay, createCompletionRelay, type RelayContext, type SessionOwnershipStore, } from "./relay.ts";
24
- export type { AgentClient, AgentSkillsResource, AgentsListOptions, AgentsResource, AgentVersionsListOptions, ArtifactsResource, AttributionInput, BlazingAgentsErrorCode, BlazingAgentsOptions, BlazingAgentsRequestOptions, BlazingAgentsUIMessage, BlazingAgentsUIMessageChunk, ChatConnectionsResource, ChatDeliveriesListOptions, ChatDeliveriesResource, ChatInput, ChatMessageInput, ChatPromptInput, ChatResult, ChatTrigger, CompletionInput, CompletionPromptIdInput, CompletionPromptInput, CompletionResult, KnownBlazingAgentsErrorCode, LatestSessionsListOptions, McpConnectionsResource, MemoriesListOptions, MemoriesResource, MerchantBindingsListOptions, MerchantBindingsResource, MerchantConnectionResource, MerchantUsageEventsListOptions, MerchantUsageEventsResource, ObjectInput, ObjectPromptIdInput, ObjectPromptInput, ObjectResult, PromptsResource, ProvidersResource, ResourceRequestOptions, ResponseObservation, SessionsResource, SkillsListOptions, TasksResource, TenantResource, TerminalStreamResult, UsageResource, UserClient, WorkspacesListOptions, WorkspacesResource, } from "./types.ts";
27
+ export type { AgentClient, AgentSkillsResource, AgentsListOptions, AgentsResource, ArtifactsResource, AttributionInput, BlazingAgentsErrorCode, BlazingAgentsOptions, BlazingAgentsRequestOptions, BlazingAgentsUIMessage, BlazingAgentsUIMessageChunk, ChatConnectionsResource, ChatDeliveriesListOptions, ChatDeliveriesResource, ChatInput, ChatMessageInput, ChatPromptInput, ChatResult, ChatTrigger, CompletionInput, CompletionPromptIdInput, CompletionPromptInput, CompletionResult, KnownBlazingAgentsErrorCode, LatestSessionsListOptions, McpConnectionsResource, MemoriesListOptions, MemoriesResource, MerchantBindingsListOptions, MerchantBindingsResource, MerchantConnectionResource, MerchantUsageEventsListOptions, MerchantUsageEventsResource, ObjectInput, ObjectPromptIdInput, ObjectPromptInput, ObjectResult, PromptsResource, ProvidersResource, ResourceRequestOptions, ResponseObservation, ResumeChatInput, SessionsResource, SkillsListOptions, TasksResource, TenantResource, TerminalStreamResult, UsageResource, UserClient, WorkspacesListOptions, WorkspacesResource, } from "./types.ts";
package/dist/index.js CHANGED
@@ -6,6 +6,9 @@
6
6
  // biome-ignore lint/performance/noBarrelFile: package entry point
7
7
  export { BlazingAgentsChatTransport, } from "./chat-transport.js";
8
8
  export { BlazingAgents } from "./client.js";
9
+ export { agentConfigSchema } from "./contracts/entities/agents.js";
10
+ export { sessionResponseSchema } from "./contracts/entities/sessions.js";
9
11
  export { BlazingAgentsDirectChatTransport, } from "./direct-chat-transport.js";
10
12
  export { BlazingAgentsError } from "./errors.js";
13
+ export { defineFunction, } from "./functions.js";
11
14
  export { createChatRelay, createCompletionRelay, } from "./relay.js";
package/dist/relay.d.ts CHANGED
@@ -1,20 +1,26 @@
1
1
  import type { BlazingAgents } from "./client.ts";
2
+ import type { ChatFunctions } from "./functions.ts";
2
3
  export interface RelayContext {
3
4
  agentId: string;
5
+ /** Handlers for this authenticated request; they also resume approved continuations. */
6
+ functions?: ChatFunctions;
4
7
  metadata?: Record<string, unknown>;
5
8
  userId: string;
6
- version?: number;
7
9
  }
8
10
  export interface SessionOwnershipStore {
9
11
  ownerOf(sessionId: string): Promise<string | undefined>;
10
12
  recordOwner(sessionId: string, userId: string): Promise<void>;
11
13
  }
12
- interface RelayOptions {
13
- client: Pick<BlazingAgents, "chat" | "completion">;
14
+ interface RelayOptions<Method extends keyof BlazingAgents> {
15
+ client: Pick<BlazingAgents, Method>;
14
16
  resolveContext(request: Request): Promise<RelayContext | null>;
15
17
  }
16
- export declare function createChatRelay(options: RelayOptions & {
18
+ /**
19
+ * Relays useChat submissions. An assistant message carrying approval
20
+ * responses records each decision and then streams the continuation.
21
+ */
22
+ export declare function createChatRelay(options: RelayOptions<"chat" | "resumeChat" | "sessions"> & {
17
23
  sessions: SessionOwnershipStore;
18
24
  }): (request: Request) => Promise<Response>;
19
- export declare function createCompletionRelay(options: RelayOptions): (request: Request) => Promise<Response>;
25
+ export declare function createCompletionRelay(options: RelayOptions<"completion">): (request: Request) => Promise<Response>;
20
26
  export {};
package/dist/relay.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import { safeValidateUIMessages } from "ai";
2
2
  import { z } from "zod";
3
+ import { decideApprovalResponses } from "./approvals.js";
3
4
  import { sessionIdSchema } from "./contracts/ids.js";
4
5
  import { BlazingAgentsError } from "./errors.js";
5
6
  const chatBodySchema = z.object({
@@ -11,6 +12,10 @@ const chatBodySchema = z.object({
11
12
  .default("submit-message"),
12
13
  });
13
14
  const completionBodySchema = z.object({ prompt: z.string().trim().min(1) });
15
+ /**
16
+ * Relays useChat submissions. An assistant message carrying approval
17
+ * responses records each decision and then streams the continuation.
18
+ */
14
19
  export function createChatRelay(options) {
15
20
  return async (request) => {
16
21
  try {
@@ -29,9 +34,20 @@ export function createChatRelay(options) {
29
34
  (await options.sessions.ownerOf(body.sessionId)) !== context.userId) {
30
35
  return errorResponse(403, "forbidden", "Session is not available.");
31
36
  }
37
+ const message = validated.data[0];
38
+ if (message.role === "assistant") {
39
+ return await relayApprovalResponses(options.client, {
40
+ agentId: context.agentId,
41
+ functions: context.functions,
42
+ message,
43
+ request,
44
+ sessionId: body.sessionId,
45
+ });
46
+ }
32
47
  const chatInput = {
33
48
  agentId: context.agentId,
34
- message: validated.data[0],
49
+ functions: context.functions,
50
+ message,
35
51
  messageId: body.messageId,
36
52
  metadata: context.metadata,
37
53
  abortSignal: request.signal,
@@ -41,7 +57,6 @@ export function createChatRelay(options) {
41
57
  ? {
42
58
  ...chatInput,
43
59
  trigger: "submit-message",
44
- version: context.version,
45
60
  }
46
61
  : {
47
62
  ...chatInput,
@@ -68,6 +83,30 @@ export function createChatRelay(options) {
68
83
  }
69
84
  };
70
85
  }
86
+ async function relayApprovalResponses(client, { functions, message, request, ...input }) {
87
+ if (input.sessionId === undefined) {
88
+ return errorResponse(400, "invalid_request", "Tool approval requires an existing Session.");
89
+ }
90
+ const target = {
91
+ agentId: input.agentId,
92
+ sessionId: input.sessionId,
93
+ abortSignal: request.signal,
94
+ };
95
+ const continuationId = await decideApprovalResponses(client, {
96
+ ...target,
97
+ message,
98
+ });
99
+ if (continuationId === undefined) {
100
+ return errorResponse(400, "invalid_request", "The message has no tool approval responses.");
101
+ }
102
+ /** An absent registry still resumes, so missing handlers become tool errors instead of a stalled observer. */
103
+ const continuation = await client.resumeChat({
104
+ ...target,
105
+ continuationId,
106
+ functions: functions ?? {},
107
+ });
108
+ return continuation.toResponse();
109
+ }
71
110
  export function createCompletionRelay(options) {
72
111
  return async (request) => {
73
112
  try {
@@ -82,7 +121,6 @@ export function createCompletionRelay(options) {
82
121
  prompt: body.prompt,
83
122
  abortSignal: request.signal,
84
123
  userId: context.userId,
85
- version: context.version,
86
124
  });
87
125
  return result.toResponse();
88
126
  }