@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.
- package/dist/approvals.d.ts +15 -0
- package/dist/approvals.js +20 -0
- package/dist/chat-transport.js +9 -2
- package/dist/client.d.ts +6 -1
- package/dist/client.js +9 -1
- package/dist/contracts/api.d.ts +3 -3
- package/dist/contracts/api.js +1 -1
- package/dist/contracts/entities/agent-approval.d.ts +32 -0
- package/dist/contracts/entities/agent-approval.js +12 -1
- package/dist/contracts/entities/agent-tools.d.ts +6 -0
- package/dist/contracts/entities/agent-tools.js +13 -0
- package/dist/contracts/entities/agents.d.ts +2 -118
- package/dist/contracts/entities/agents.js +2 -24
- package/dist/contracts/entities/chat.d.ts +95 -24
- package/dist/contracts/entities/chat.js +105 -39
- package/dist/contracts/entities/providers.d.ts +2 -6
- package/dist/contracts/entities/providers.js +2 -9
- package/dist/contracts/entities/sessions.d.ts +112 -5
- package/dist/contracts/entities/sessions.js +7 -5
- package/dist/contracts/entities/tasks.d.ts +285 -10
- package/dist/contracts/entities/tasks.js +2 -5
- package/dist/contracts/ids.d.ts +1 -0
- package/dist/contracts/ids.js +1 -0
- package/dist/contracts/index.d.ts +8 -7
- package/dist/contracts/index.js +6 -5
- package/dist/contracts/limitations.d.ts +0 -2
- package/dist/contracts/limitations.js +0 -2
- package/dist/direct-chat-transport.d.ts +3 -1
- package/dist/direct-chat-transport.js +20 -23
- package/dist/functions.d.ts +38 -0
- package/dist/functions.js +299 -0
- package/dist/generation.d.ts +7 -1
- package/dist/generation.js +64 -20
- package/dist/index.d.ts +7 -4
- package/dist/index.js +3 -0
- package/dist/relay.d.ts +11 -5
- package/dist/relay.js +41 -3
- package/dist/resources/agents.js +1 -33
- package/dist/resources/providers.js +1 -1
- package/dist/resources/sessions.d.ts +1 -1
- package/dist/resources/sessions.js +9 -4
- package/dist/types.d.ts +22 -23
- 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
|
+
}
|
package/dist/generation.d.ts
CHANGED
|
@@ -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>;
|
package/dist/generation.js
CHANGED
|
@@ -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 {
|
|
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
|
|
95
|
-
|
|
137
|
+
let sessionId = resumeSessionId;
|
|
138
|
+
let sessionIdError;
|
|
139
|
+
if (sessionId === undefined) {
|
|
96
140
|
try {
|
|
97
|
-
|
|
141
|
+
sessionId = sessionIdFromLocation(response, response.headers.get("x-request-id") ?? undefined);
|
|
98
142
|
}
|
|
99
143
|
catch (error) {
|
|
100
|
-
|
|
144
|
+
sessionIdError = error;
|
|
101
145
|
}
|
|
102
146
|
}
|
|
103
|
-
|
|
104
|
-
|
|
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
|
-
...
|
|
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,
|
|
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,
|
|
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,
|
|
14
|
+
interface RelayOptions<Method extends keyof BlazingAgents> {
|
|
15
|
+
client: Pick<BlazingAgents, Method>;
|
|
14
16
|
resolveContext(request: Request): Promise<RelayContext | null>;
|
|
15
17
|
}
|
|
16
|
-
|
|
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
|
-
|
|
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
|
}
|