@blazingagents/sdk 0.14.0 → 0.16.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 (49) hide show
  1. package/README.md +5 -231
  2. package/dist/approvals.d.ts +15 -0
  3. package/dist/approvals.js +20 -0
  4. package/dist/chat-transport.js +9 -2
  5. package/dist/client.d.ts +8 -1
  6. package/dist/client.js +41 -1
  7. package/dist/contracts/api.d.ts +9 -9
  8. package/dist/contracts/api.js +3 -3
  9. package/dist/contracts/entities/agent-approval.d.ts +32 -0
  10. package/dist/contracts/entities/agent-approval.js +12 -1
  11. package/dist/contracts/entities/agent-tools.d.ts +6 -0
  12. package/dist/contracts/entities/agent-tools.js +13 -0
  13. package/dist/contracts/entities/agents.d.ts +4 -1
  14. package/dist/contracts/entities/agents.js +9 -6
  15. package/dist/contracts/entities/chat.d.ts +95 -12
  16. package/dist/contracts/entities/chat.js +105 -36
  17. package/dist/contracts/entities/prompts.d.ts +9 -1
  18. package/dist/contracts/entities/prompts.js +14 -4
  19. package/dist/contracts/entities/sessions.d.ts +6 -0
  20. package/dist/contracts/entities/sessions.js +2 -2
  21. package/dist/contracts/entities/tasks.d.ts +4 -6
  22. package/dist/contracts/entities/tasks.js +4 -6
  23. package/dist/contracts/entities/usage.d.ts +18 -0
  24. package/dist/contracts/entities/usage.js +28 -0
  25. package/dist/contracts/ids.d.ts +1 -0
  26. package/dist/contracts/ids.js +1 -0
  27. package/dist/contracts/index.d.ts +11 -10
  28. package/dist/contracts/index.js +8 -7
  29. package/dist/contracts/limitations.d.ts +4 -4
  30. package/dist/contracts/limitations.js +4 -4
  31. package/dist/direct-chat-transport.d.ts +3 -0
  32. package/dist/direct-chat-transport.js +20 -22
  33. package/dist/functions.d.ts +38 -0
  34. package/dist/functions.js +299 -0
  35. package/dist/generation.d.ts +7 -1
  36. package/dist/generation.js +64 -12
  37. package/dist/http.js +3 -0
  38. package/dist/index.d.ts +6 -4
  39. package/dist/index.js +1 -0
  40. package/dist/relay.d.ts +11 -4
  41. package/dist/relay.js +41 -1
  42. package/dist/resources/agents.d.ts +0 -6
  43. package/dist/resources/agents.js +2 -6
  44. package/dist/resources/prompts.d.ts +0 -4
  45. package/dist/resources/prompts.js +2 -6
  46. package/dist/resources/sessions.js +4 -2
  47. package/dist/resources/usage.js +9 -1
  48. package/dist/types.d.ts +39 -1
  49. package/package.json +8 -6
@@ -1,21 +1,22 @@
1
1
  /** Public runtime schemas for the Blazing Agents HTTP API. */
2
2
  // biome-ignore lint/performance/noBarrelFile: public contract entry point
3
3
  export { receivedApiErrorResponseSchema } from "./api.js";
4
- export { approvalDecisionSchema, approvalPolicySchema, toolReferenceSchema, } from "./entities/agent-approval.js";
5
- export { agentResponseSchema, agentSchema, agentStatusSchema, agentsResponseSchema, agentVersionSchema, agentVersionsResponseSchema, createAgentBodySchema, updateAgentBodySchema, } from "./entities/agents.js";
4
+ export { approvalDecisionSchema, approvalPolicySchema, toolExecutionReferenceSchema, toolReferenceSchema, } from "./entities/agent-approval.js";
5
+ export { chatFunctionNameSchema } from "./entities/agent-tools.js";
6
+ export { agentResponseSchema, agentSchema, agentStatusSchema, agentsListQuerySchema, agentsResponseSchema, agentVersionSchema, agentVersionsResponseSchema, createAgentBodySchema, updateAgentBodySchema, } from "./entities/agents.js";
6
7
  export { artifactDownloadUrlResponseSchema, artifactListItemSchema, artifactsListResponseSchema, } from "./entities/artifacts.js";
7
8
  export { attributionCreateInputSchema, metadataSchema, } from "./entities/attribution.js";
8
- export { blazingAgentsChatMessageMetadataSchema, chatModeSchema, chatRequestBodySchema, chatTriggerSchema, generationRequestBodySchema, jsonSchemaShapeSchema, promptVariablesSchema, usageSummarySchema, } from "./entities/chat.js";
9
+ export { blazingAgentsChatMessageMetadataSchema, chatFunctionCallEventSchema, chatFunctionDefinitionSchema, chatFunctionDefinitionsSchema, chatFunctionOutcomeSchema, chatModeSchema, chatRequestBodySchema, chatTriggerSchema, claimChatFunctionBodySchema, claimChatFunctionResponseSchema, generationRequestBodySchema, jsonSchemaShapeSchema, MAX_CHAT_FUNCTION_DEFINITIONS_BYTES, MAX_CHAT_FUNCTION_PAYLOAD_BYTES, MAX_CHAT_FUNCTIONS, promptVariablesSchema, resolveChatFunctionBodySchema, resolveChatFunctionResponseSchema, resumeToolApprovalContinuationBodySchema, usageSummarySchema, } from "./entities/chat.js";
9
10
  export { chatConfigurationSchema, chatConnectionSchema, chatConnectionsResponseSchema, chatCredentialsSchema, chatDeliveriesResponseSchema, chatDeliveryListStatusSchema, chatDeliverySchema, chatDeliveryStatusSchema, chatHealthCheckSchema, chatHealthSchema, chatIdentitySchema, createChatConnectionBodySchema, rotateChatConnectionBodySchema, tenantChatDeliverySchema, updateChatConnectionBodySchema, } from "./entities/chat-connections.js";
10
11
  export { approveMcpOauthAuthorizationBodySchema, createMcpConnectionBodySchema, mcpAttachmentResponseSchema, mcpAttachmentsResponseSchema, mcpConnectionAuthTypeSchema, mcpConnectionOauthConnectResponseSchema, mcpConnectionReconnectResultSchema, mcpConnectionResponseSchema, mcpConnectionStatusSchema, mcpConnectionsResponseSchema, mcpConnectionTestErrorCodeSchema, mcpConnectionTestResponseSchema, mcpOauthAuthorizationLaunchResponseSchema, reconnectMcpConnectionBodySchema, updateMcpAttachmentBodySchema, updateMcpConnectionBodySchema, } from "./entities/mcp-connections.js";
11
12
  export { createMemoryBodySchema, memoriesListResponseSchema, memoryResponseSchema, memorySchema, updateMemoryBodySchema, } from "./entities/memories.js";
12
13
  export { createMerchantConnectionBodySchema, merchantBindingResponseSchema, merchantBindingsResponseSchema, merchantConnectionResponseSchema, merchantConnectionSchema, merchantConnectionStatusSchema, merchantCustomerBindingSchema, merchantEnvironmentSchema, merchantGuardSchema, merchantProviderSchema, merchantUsageEventNextAction, merchantUsageEventNextActionSchema, merchantUsageEventSchema, merchantUsageEventStatusSchema, merchantUsageEventsResponseSchema, merchantUsageSummaryQuerySchema, merchantUsageSummaryResponseSchema, merchantUsageSummarySchema, merchantWorkflowIssueSchema, updateMerchantConnectionBodySchema, upsertMerchantBindingBodySchema, } from "./entities/merchant.js";
13
- export { createPromptBodySchema, parsePromptVariables, promptResponseSchema, promptSchema, promptsResponseSchema, promptTemplateSchema, renderPromptTemplate, updatePromptBodySchema, } from "./entities/prompts.js";
14
+ export { createPromptBodySchema, parsePromptVariables, promptResponseSchema, promptSchema, promptsListQuerySchema, promptsResponseSchema, promptTemplateSchema, renderPromptTemplate, updatePromptBodySchema, } from "./entities/prompts.js";
14
15
  export { createProviderBodySchema, providerListItemSchema, providerModelsResponseSchema, providerResponseSchema, providersResponseSchema, providerTypeSchema, thinkingLevelsResponseSchema, updateProviderBodySchema, } from "./entities/providers.js";
15
16
  export { decideToolApprovalBodySchema, latestSessionListItemSchema, latestSessionsListResponseSchema, sessionListItemSchema, sessionMessageSchema, sessionMessagesResponseSchema, sessionsListResponseSchema, toolApprovalContinuationStateSchema, toolApprovalDecisionResponseSchema, toolApprovalStateSchema, toolApprovalsResponseSchema, } from "./entities/sessions.js";
16
17
  export { copySkillBodySchema, createSkillBodySchema, skillCopyResultsSchema, skillDetailSchema, skillsListResponseSchema, } from "./entities/skills.js";
17
- export { createTaskBodySchema, createTaskResponseSchema, createTaskRunBodySchema, createTaskRunResponseSchema, taskListItemSchema, taskResponseSchema, taskRunMessagesResponseSchema, taskRunResponseSchema, taskRunSchema, taskRunStatusSchema, taskRunsListResponseSchema, taskScheduleKindSchema, taskSchema, tasksListResponseSchema, updateTaskBodySchema, } from "./entities/tasks.js";
18
+ export { createTaskBodySchema, createTaskResponseSchema, createTaskRunBodySchema, createTaskRunResponseSchema, taskCronConfigSchema, taskIntervalConfigSchema, taskListItemSchema, taskOnceConfigSchema, taskResponseSchema, taskRunMessagesResponseSchema, taskRunResponseSchema, taskRunSchema, taskRunStatusSchema, taskRunsListResponseSchema, taskScheduleInputSchema, taskScheduleKindSchema, taskSchema, tasksListResponseSchema, updateTaskBodySchema, } from "./entities/tasks.js";
18
19
  export { quotaSchema, subscriptionStatusSchema, tenantDeletionSchema, tenantResponseSchema, tenantSchema, tenantSettingsResponseSchema, updateTenantSettingsBodySchema, } from "./entities/tenants.js";
19
- export { usageOverviewQuerySchema, usageOverviewResponseSchema, usageResponseSchema, } from "./entities/usage.js";
20
+ export { sessionUsageQuerySchema, sessionUsageResponseSchema, usageOverviewQuerySchema, usageOverviewResponseSchema, usageResponseSchema, } from "./entities/usage.js";
20
21
  export { createWorkspaceBodySchema, updateWorkspaceBodySchema, workspaceSchema, workspacesListResponseSchema, } from "./entities/workspaces.js";
21
- export { agentIdSchema, apiKeyTokenSchema, isAdminAgentId, promptIdSchema, sessionIdSchema, } from "./ids.js";
22
+ export { agentIdSchema, apiKeyTokenSchema, functionCallIdSchema, isAdminAgentId, promptIdSchema, sessionIdSchema, } from "./ids.js";
@@ -29,10 +29,8 @@ export declare const MAX_SKILL_COMPATIBILITY_LENGTH = 500;
29
29
  export declare const MAX_SKILL_COPY_DESTINATIONS = 30;
30
30
  export declare const DEFAULT_SKILLS_LIST_LIMIT = 50;
31
31
  export declare const MAX_SKILLS_LIST_LIMIT = 100;
32
- /**
33
- * Prompts — max 100 per tenant, ~10 KB template cap, name ≤ 80.
34
- */
35
- export declare const MAX_PROMPTS_PER_TENANT = 100;
32
+ export declare const DEFAULT_PROMPTS_LIST_LIMIT = 50;
33
+ export declare const MAX_PROMPTS_LIST_LIMIT = 100;
36
34
  export declare const MAX_PROMPT_TEMPLATE_BYTES: number;
37
35
  export declare const MAX_PROMPT_VARIABLES = 10;
38
36
  export declare const MAX_PROMPT_NAME_LENGTH = 80;
@@ -73,6 +71,8 @@ export declare const MAX_MCP_ATTACHMENT_METADATA_KEY_LENGTH = 64;
73
71
  export declare const MAX_MCP_REQUEST_CONTEXT_BYTES: number;
74
72
  /** Agents — name ≤ 80, instructions ≤ 3000. */
75
73
  export declare const MAX_AGENT_NAME_LENGTH = 80;
74
+ export declare const DEFAULT_AGENTS_LIST_LIMIT = 50;
75
+ export declare const MAX_AGENTS_LIST_LIMIT = 100;
76
76
  export declare const MAX_AGENT_INSTRUCTIONS_LENGTH = 3000;
77
77
  export declare const DEFAULT_AGENT_VERSIONS_LIST_LIMIT = 50;
78
78
  export declare const MAX_AGENT_VERSIONS_LIST_LIMIT = 200;
@@ -30,10 +30,8 @@ export const MAX_SKILL_COMPATIBILITY_LENGTH = 500;
30
30
  export const MAX_SKILL_COPY_DESTINATIONS = 30;
31
31
  export const DEFAULT_SKILLS_LIST_LIMIT = 50;
32
32
  export const MAX_SKILLS_LIST_LIMIT = 100;
33
- /**
34
- * Prompts — max 100 per tenant, ~10 KB template cap, name ≤ 80.
35
- */
36
- export const MAX_PROMPTS_PER_TENANT = 100;
33
+ export const DEFAULT_PROMPTS_LIST_LIMIT = 50;
34
+ export const MAX_PROMPTS_LIST_LIMIT = 100;
37
35
  export const MAX_PROMPT_TEMPLATE_BYTES = 10 * 1024;
38
36
  export const MAX_PROMPT_VARIABLES = 10;
39
37
  export const MAX_PROMPT_NAME_LENGTH = 80;
@@ -74,6 +72,8 @@ export const MAX_MCP_ATTACHMENT_METADATA_KEY_LENGTH = 64;
74
72
  export const MAX_MCP_REQUEST_CONTEXT_BYTES = 16 * 1024;
75
73
  /** Agents — name ≤ 80, instructions ≤ 3000. */
76
74
  export const MAX_AGENT_NAME_LENGTH = 80;
75
+ export const DEFAULT_AGENTS_LIST_LIMIT = 50;
76
+ export const MAX_AGENTS_LIST_LIMIT = 100;
77
77
  export const MAX_AGENT_INSTRUCTIONS_LENGTH = 3000;
78
78
  export const DEFAULT_AGENT_VERSIONS_LIST_LIMIT = 50;
79
79
  export const MAX_AGENT_VERSIONS_LIST_LIMIT = 200;
@@ -1,7 +1,10 @@
1
1
  import { type ChatTransport, DefaultChatTransport, type UIMessage } from "ai";
2
2
  import type { BlazingAgents } from "./client.ts";
3
+ import type { ChatFunctions } from "./functions.ts";
3
4
  export type BlazingAgentsDirectChatTransportOptions = {
4
5
  agentId: string;
6
+ /** Caller-local functions for every Turn and approved continuation of this chat. */
7
+ functions?: ChatFunctions;
5
8
  getClient: () => BlazingAgents | Promise<BlazingAgents>;
6
9
  metadata?: Record<string, unknown>;
7
10
  /** Receives the Session ID before the response stream is consumed. */
@@ -1,4 +1,5 @@
1
- import { DefaultChatTransport, isToolUIPart, lastAssistantMessageIsCompleteWithApprovalResponses, } from "ai";
1
+ import { DefaultChatTransport, lastAssistantMessageIsCompleteWithApprovalResponses, } from "ai";
2
+ import { decideApprovalResponses } from "./approvals.js";
2
3
  import { sessionIdSchema } from "./contracts/ids.js";
3
4
  /** Connects useChat directly to the SDK, including native streaming fetch clients. */
4
5
  export class BlazingAgentsDirectChatTransport extends DefaultChatTransport {
@@ -26,6 +27,7 @@ export class BlazingAgentsDirectChatTransport extends DefaultChatTransport {
26
27
  const client = await this.#options.getClient();
27
28
  const common = {
28
29
  agentId: this.#options.agentId,
30
+ functions: this.#options.functions,
29
31
  messageId: input.messageId,
30
32
  abortSignal: input.abortSignal,
31
33
  };
@@ -83,17 +85,19 @@ export class BlazingAgentsDirectChatTransport extends DefaultChatTransport {
83
85
  if (this.#sessionId === undefined) {
84
86
  return null;
85
87
  }
86
- const { continuation } = await (await this.#options.getClient()).sessions.toolApprovals({
88
+ const client = await this.#options.getClient();
89
+ const { continuation } = await client.sessions.toolApprovals({
87
90
  agentId: this.#options.agentId,
88
91
  sessionId: this.#sessionId,
89
92
  });
90
93
  if (continuation?.state !== "queued" && continuation?.state !== "running") {
91
94
  return null;
92
95
  }
93
- const result = await (await this.#options.getClient()).sessions.joinToolApprovalContinuation({
96
+ const result = await client.resumeChat({
94
97
  agentId: this.#options.agentId,
95
98
  sessionId: this.#sessionId,
96
99
  continuationId: continuation.id,
100
+ functions: this.#options.functions ?? {},
97
101
  });
98
102
  return this.processResponseStream(result.toStream());
99
103
  }
@@ -101,29 +105,23 @@ export class BlazingAgentsDirectChatTransport extends DefaultChatTransport {
101
105
  if (this.#sessionId === undefined) {
102
106
  throw new Error("Tool approval requires an existing Session.");
103
107
  }
104
- const lastStep = last.parts.findLastIndex((part) => part.type === "step-start");
105
- let continuationId;
106
- for (const part of last.parts.slice(lastStep + 1)) {
107
- if (isToolUIPart(part) && part.state === "approval-responded") {
108
- const decision = await (await this.#options.getClient()).sessions.decideToolApproval({
109
- agentId: this.#options.agentId,
110
- sessionId: this.#sessionId,
111
- approvalId: part.approval.id,
112
- approved: part.approval.approved,
113
- reason: part.approval.reason,
114
- abortSignal: input.abortSignal,
115
- });
116
- continuationId = decision.continuationId;
117
- }
118
- }
108
+ const client = await this.#options.getClient();
109
+ const target = {
110
+ agentId: this.#options.agentId,
111
+ sessionId: this.#sessionId,
112
+ abortSignal: input.abortSignal,
113
+ };
114
+ const continuationId = await decideApprovalResponses(client, {
115
+ ...target,
116
+ message: last,
117
+ });
119
118
  if (continuationId === undefined) {
120
119
  throw new Error("Tool approval response is missing an approval.");
121
120
  }
122
- const continuation = await (await this.#options.getClient()).sessions.joinToolApprovalContinuation({
123
- agentId: this.#options.agentId,
124
- sessionId: this.#sessionId,
121
+ const continuation = await client.resumeChat({
122
+ ...target,
125
123
  continuationId,
126
- abortSignal: input.abortSignal,
124
+ functions: this.#options.functions ?? {},
127
125
  });
128
126
  return this.processResponseStream(continuation.toStream());
129
127
  }
@@ -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 = {};
@@ -83,32 +131,36 @@ function sessionIdFromLocation(response, requestId) {
83
131
  }
84
132
  return parsed.data;
85
133
  }
86
- function buildChatResult(response, resumeSessionId) {
87
- const terminalResult = buildTerminalStreamResult(response, "chat");
134
+ function buildChatResult(config, response, resumeSessionId, dispatch) {
88
135
  /**
89
136
  * `sessionId` resolves from the `Location` header on create, or to the
90
137
  * passed id on resume. Read eagerly (the header is available before the
91
138
  * body streams) so awaiting `result.sessionId` does not depend on
92
139
  * draining the stream.
93
140
  */
94
- let sessionIdPromise;
95
- if (resumeSessionId === undefined) {
141
+ let sessionId = resumeSessionId;
142
+ let sessionIdError;
143
+ if (sessionId === undefined) {
96
144
  try {
97
- sessionIdPromise = Promise.resolve(sessionIdFromLocation(response, terminalResult.requestId));
145
+ sessionId = sessionIdFromLocation(response, response.headers.get("x-request-id") ?? undefined);
98
146
  }
99
147
  catch (error) {
100
- sessionIdPromise = Promise.reject(error);
148
+ sessionIdError = error;
101
149
  }
102
150
  }
103
- else {
104
- sessionIdPromise = Promise.resolve(resumeSessionId);
105
- }
151
+ const sessionIdPromise = sessionId === undefined
152
+ ? Promise.reject(sessionIdError)
153
+ : Promise.resolve(sessionId);
106
154
  sessionIdPromise.catch(() => {
107
155
  /* no-op — prevents unhandled rejection if the caller never awaits */
108
156
  });
157
+ const { functions } = dispatch;
158
+ const streamResponse = functions && sessionId !== undefined
159
+ ? withFilteredBody(response, (body) => dispatchChatFunctions(config, { ...dispatch, functions, sessionId }, body))
160
+ : response;
109
161
  return {
110
162
  sessionId: sessionIdPromise,
111
- ...terminalResult,
163
+ ...buildTerminalStreamResult(streamResponse, "chat"),
112
164
  };
113
165
  }
114
166
  export function buildTerminalStreamResult(response, resourceName) {
package/dist/http.js CHANGED
@@ -76,6 +76,9 @@ async function rawRequest(config, path, options) {
76
76
  ...(clientRequestId === undefined
77
77
  ? {}
78
78
  : { "x-client-request-id": clientRequestId }),
79
+ ...(config.scopeUserId === undefined
80
+ ? {}
81
+ : { "X-BA-User-Id": config.scopeUserId }),
79
82
  };
80
83
  let body = options.body ?? null;
81
84
  if (options.json !== undefined) {