@bowenqt/qiniu-ai-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 (107) hide show
  1. package/dist/ai/agent-graph.d.ts +8 -0
  2. package/dist/ai/agent-graph.d.ts.map +1 -1
  3. package/dist/ai/agent-graph.js +23 -3
  4. package/dist/ai/agent-graph.js.map +1 -1
  5. package/dist/ai/agent-graph.mjs +23 -3
  6. package/dist/ai/create-agent.d.ts +101 -0
  7. package/dist/ai/create-agent.d.ts.map +1 -0
  8. package/dist/ai/create-agent.js +88 -0
  9. package/dist/ai/create-agent.js.map +1 -0
  10. package/dist/ai/create-agent.mjs +85 -0
  11. package/dist/ai/generate-object.d.ts +99 -0
  12. package/dist/ai/generate-object.d.ts.map +1 -0
  13. package/dist/ai/generate-object.js +237 -0
  14. package/dist/ai/generate-object.js.map +1 -0
  15. package/dist/ai/generate-object.mjs +201 -0
  16. package/dist/ai/generate-text.d.ts +13 -1
  17. package/dist/ai/generate-text.d.ts.map +1 -1
  18. package/dist/ai/generate-text.js +35 -5
  19. package/dist/ai/generate-text.js.map +1 -1
  20. package/dist/ai/generate-text.mjs +35 -5
  21. package/dist/ai/graph/checkpointer.d.ts +72 -2
  22. package/dist/ai/graph/checkpointer.d.ts.map +1 -1
  23. package/dist/ai/graph/checkpointer.js +99 -1
  24. package/dist/ai/graph/checkpointer.js.map +1 -1
  25. package/dist/ai/graph/checkpointer.mjs +96 -1
  26. package/dist/ai/graph/index.d.ts +2 -2
  27. package/dist/ai/graph/index.d.ts.map +1 -1
  28. package/dist/ai/graph/index.js +5 -1
  29. package/dist/ai/graph/index.js.map +1 -1
  30. package/dist/ai/graph/index.mjs +3 -1
  31. package/dist/ai/graph/postgres-checkpointer.d.ts +1 -1
  32. package/dist/ai/graph/postgres-checkpointer.d.ts.map +1 -1
  33. package/dist/ai/graph/postgres-checkpointer.js +13 -3
  34. package/dist/ai/graph/postgres-checkpointer.js.map +1 -1
  35. package/dist/ai/graph/postgres-checkpointer.mjs +13 -3
  36. package/dist/ai/graph/redis-checkpointer.d.ts +1 -1
  37. package/dist/ai/graph/redis-checkpointer.d.ts.map +1 -1
  38. package/dist/ai/graph/redis-checkpointer.js +6 -2
  39. package/dist/ai/graph/redis-checkpointer.js.map +1 -1
  40. package/dist/ai/graph/redis-checkpointer.mjs +6 -2
  41. package/dist/ai/internal-types.d.ts +14 -0
  42. package/dist/ai/internal-types.d.ts.map +1 -1
  43. package/dist/ai/internal-types.js +15 -0
  44. package/dist/ai/internal-types.js.map +1 -1
  45. package/dist/ai/internal-types.mjs +13 -0
  46. package/dist/ai/memory/index.d.ts +147 -0
  47. package/dist/ai/memory/index.d.ts.map +1 -0
  48. package/dist/ai/memory/index.js +240 -0
  49. package/dist/ai/memory/index.js.map +1 -0
  50. package/dist/ai/memory/index.mjs +234 -0
  51. package/dist/ai/nodes/execute-node.d.ts +2 -1
  52. package/dist/ai/nodes/execute-node.d.ts.map +1 -1
  53. package/dist/ai/nodes/execute-node.js +11 -33
  54. package/dist/ai/nodes/execute-node.js.map +1 -1
  55. package/dist/ai/nodes/execute-node.mjs +11 -33
  56. package/dist/ai/nodes/memory-node.d.ts.map +1 -1
  57. package/dist/ai/nodes/memory-node.js +14 -16
  58. package/dist/ai/nodes/memory-node.js.map +1 -1
  59. package/dist/ai/nodes/memory-node.mjs +15 -17
  60. package/dist/ai/stream-object.d.ts +109 -0
  61. package/dist/ai/stream-object.d.ts.map +1 -0
  62. package/dist/ai/stream-object.js +383 -0
  63. package/dist/ai/stream-object.js.map +1 -0
  64. package/dist/ai/stream-object.mjs +347 -0
  65. package/dist/ai/tool-approval.d.ts +90 -0
  66. package/dist/ai/tool-approval.d.ts.map +1 -0
  67. package/dist/ai/tool-approval.js +151 -0
  68. package/dist/ai/tool-approval.js.map +1 -0
  69. package/dist/ai/tool-approval.mjs +147 -0
  70. package/dist/index.d.ts +17 -5
  71. package/dist/index.d.ts.map +1 -1
  72. package/dist/index.js +28 -3
  73. package/dist/index.js.map +1 -1
  74. package/dist/index.mjs +16 -4
  75. package/dist/lib/capability-cache.d.ts +72 -0
  76. package/dist/lib/capability-cache.d.ts.map +1 -0
  77. package/dist/lib/capability-cache.js +117 -0
  78. package/dist/lib/capability-cache.js.map +1 -0
  79. package/dist/lib/capability-cache.mjs +113 -0
  80. package/dist/lib/content-converter.d.ts +33 -0
  81. package/dist/lib/content-converter.d.ts.map +1 -0
  82. package/dist/lib/content-converter.js +166 -0
  83. package/dist/lib/content-converter.js.map +1 -0
  84. package/dist/lib/content-converter.mjs +161 -0
  85. package/dist/lib/errors.d.ts +15 -0
  86. package/dist/lib/errors.d.ts.map +1 -1
  87. package/dist/lib/errors.js +13 -1
  88. package/dist/lib/errors.js.map +1 -1
  89. package/dist/lib/errors.mjs +11 -0
  90. package/dist/lib/messages.js +4 -3
  91. package/dist/lib/messages.js.map +1 -1
  92. package/dist/lib/messages.mjs +4 -3
  93. package/dist/lib/partial-json-parser.d.ts +63 -0
  94. package/dist/lib/partial-json-parser.d.ts.map +1 -0
  95. package/dist/lib/partial-json-parser.js +142 -0
  96. package/dist/lib/partial-json-parser.js.map +1 -0
  97. package/dist/lib/partial-json-parser.mjs +137 -0
  98. package/dist/lib/token-estimator.d.ts.map +1 -1
  99. package/dist/lib/token-estimator.js +3 -2
  100. package/dist/lib/token-estimator.js.map +1 -1
  101. package/dist/lib/token-estimator.mjs +3 -2
  102. package/dist/lib/tool-registry.d.ts +21 -0
  103. package/dist/lib/tool-registry.d.ts.map +1 -1
  104. package/dist/lib/tool-registry.js.map +1 -1
  105. package/dist/lib/types.d.ts +20 -4
  106. package/dist/lib/types.d.ts.map +1 -1
  107. package/package.json +1 -1
@@ -0,0 +1,347 @@
1
+ /**
2
+ * streamObject - Stream structured output with incremental parsing.
3
+ * Bypasses predict-node to enable streaming with JSON schema.
4
+ *
5
+ * @example
6
+ * ```typescript
7
+ * import { streamObject } from '@bowenqt/qiniu-ai-sdk';
8
+ * import { z } from 'zod';
9
+ *
10
+ * const { partialObjectStream, object } = await streamObject({
11
+ * client,
12
+ * model: 'gemini-2.5-flash',
13
+ * schema: z.object({
14
+ * title: z.string(),
15
+ * chapters: z.array(z.object({
16
+ * title: z.string(),
17
+ * content: z.string(),
18
+ * })),
19
+ * }),
20
+ * prompt: 'Generate a book outline about AI',
21
+ * });
22
+ *
23
+ * // Stream partial objects
24
+ * for await (const partial of partialObjectStream) {
25
+ * console.log(partial);
26
+ * }
27
+ *
28
+ * // Get final validated object
29
+ * const result = await object;
30
+ * ```
31
+ */
32
+ import { StructuredOutputError } from '../lib/errors.mjs';
33
+ import { PartialJsonParser } from '../lib/partial-json-parser.mjs';
34
+ import { capabilityCache } from '../lib/capability-cache.mjs';
35
+ import { generateObject } from './generate-object.mjs';
36
+ import { normalizeContent } from '../lib/content-converter.mjs';
37
+ // ============================================================================
38
+ // JSON Schema Conversion (reuse from generate-object)
39
+ // ============================================================================
40
+ // Dynamic import cache for zod-to-json-schema
41
+ let loadAttempted = false;
42
+ let cachedZodToJsonSchema = null;
43
+ async function zodToJsonSchemaAsync(schema) {
44
+ if (!loadAttempted) {
45
+ loadAttempted = true;
46
+ try {
47
+ const zodToJsonSchemaModule = await import('zod-to-json-schema');
48
+ cachedZodToJsonSchema = zodToJsonSchemaModule.zodToJsonSchema;
49
+ }
50
+ catch {
51
+ cachedZodToJsonSchema = null;
52
+ }
53
+ }
54
+ if (cachedZodToJsonSchema) {
55
+ const result = cachedZodToJsonSchema(schema);
56
+ const { $schema, ...rest } = result;
57
+ return rest;
58
+ }
59
+ return parseZodSchemaToJsonSchema(schema);
60
+ }
61
+ function parseZodSchemaToJsonSchema(schema) {
62
+ const def = schema._def;
63
+ if (!def) {
64
+ return { type: 'object' };
65
+ }
66
+ const typeName = def.typeName;
67
+ switch (typeName) {
68
+ case 'ZodString':
69
+ return { type: 'string' };
70
+ case 'ZodNumber':
71
+ return { type: 'number' };
72
+ case 'ZodBoolean':
73
+ return { type: 'boolean' };
74
+ case 'ZodArray':
75
+ return {
76
+ type: 'array',
77
+ items: def.type ? parseZodSchemaToJsonSchema(def.type) : {},
78
+ };
79
+ case 'ZodObject': {
80
+ const shape = def.shape?.() ?? {};
81
+ const properties = {};
82
+ const required = [];
83
+ for (const [key, value] of Object.entries(shape)) {
84
+ properties[key] = parseZodSchemaToJsonSchema(value);
85
+ const innerDef = value?._def;
86
+ if (innerDef?.typeName !== 'ZodOptional') {
87
+ required.push(key);
88
+ }
89
+ }
90
+ return {
91
+ type: 'object',
92
+ properties,
93
+ ...(required.length > 0 ? { required } : {}),
94
+ };
95
+ }
96
+ default:
97
+ return { type: 'object' };
98
+ }
99
+ }
100
+ // ============================================================================
101
+ // Error Classification
102
+ // ============================================================================
103
+ /**
104
+ * Check if error indicates unsupported streaming format.
105
+ */
106
+ function isUnsupportedStreamError(error) {
107
+ // Check error code
108
+ const code = error?.code;
109
+ if (code === 'UNSUPPORTED_STREAM_FORMAT' ||
110
+ code === 'INVALID_RESPONSE_FORMAT' ||
111
+ code === 'unsupported_response_format') {
112
+ return true;
113
+ }
114
+ // Check HTTP status
115
+ const status = error?.status;
116
+ if (status === 400 || status === 422) {
117
+ const message = String(error?.message ?? '').toLowerCase();
118
+ if (message.includes('stream') ||
119
+ message.includes('response_format') ||
120
+ message.includes('json_schema')) {
121
+ return true;
122
+ }
123
+ }
124
+ return false;
125
+ }
126
+ // ============================================================================
127
+ // Main Function
128
+ // ============================================================================
129
+ /**
130
+ * Stream structured output with JSON schema validation.
131
+ */
132
+ export async function streamObject(options) {
133
+ const { client, model, schema, allowFallback = false } = options;
134
+ // Check capability cache
135
+ if (capabilityCache.isNotSupported(client, model, 'stream_json_schema')) {
136
+ if (allowFallback) {
137
+ return createFallbackResult(options);
138
+ }
139
+ throw new Error(`Model ${model} does not support streaming JSON schema. Set allowFallback: true to use non-streaming.`);
140
+ }
141
+ // Try streaming
142
+ try {
143
+ return await streamObjectInternal(options);
144
+ }
145
+ catch (error) {
146
+ if (isUnsupportedStreamError(error)) {
147
+ // Cache the failure
148
+ capabilityCache.set(client, model, 'stream_json_schema', false);
149
+ if (allowFallback) {
150
+ console.warn('streamObject: API does not support streaming JSON schema, falling back to non-streaming');
151
+ return createFallbackResult(options);
152
+ }
153
+ }
154
+ throw error;
155
+ }
156
+ }
157
+ /**
158
+ * Internal streaming implementation.
159
+ */
160
+ async function streamObjectInternal(options) {
161
+ const { client, model, schema, prompt, messages, system, temperature, topP, maxTokens, abortSignal } = options;
162
+ // Build messages
163
+ const apiMessages = [];
164
+ if (system) {
165
+ apiMessages.push({ role: 'system', content: system });
166
+ }
167
+ if (messages) {
168
+ apiMessages.push(...messages);
169
+ }
170
+ if (prompt) {
171
+ apiMessages.push({ role: 'user', content: prompt });
172
+ }
173
+ // Convert schema to JSON Schema
174
+ const jsonSchema = await zodToJsonSchemaAsync(schema);
175
+ // Build response format
176
+ const responseFormat = {
177
+ type: 'json_schema',
178
+ json_schema: {
179
+ name: 'response',
180
+ strict: true,
181
+ schema: jsonSchema,
182
+ },
183
+ };
184
+ // Normalize multimodal content (image -> image_url) for API compatibility
185
+ const normalizedMessages = apiMessages.map(msg => ({
186
+ ...msg,
187
+ content: normalizeContent(msg.content),
188
+ }));
189
+ // Create streaming request - bypass predict-node, go directly to client
190
+ const response = await client.chat.createStream({
191
+ model,
192
+ messages: normalizedMessages,
193
+ temperature,
194
+ top_p: topP,
195
+ max_tokens: maxTokens,
196
+ response_format: responseFormat,
197
+ }, { signal: abortSignal });
198
+ // NOTE: capabilityCache is set AFTER stream completes successfully (in background consumer)
199
+ // Create parser and state
200
+ const parser = new PartialJsonParser();
201
+ let rawText = '';
202
+ let usage;
203
+ let resolveObject;
204
+ let rejectObject;
205
+ let resolveRaw;
206
+ let resolveUsage;
207
+ const objectPromise = new Promise((resolve, reject) => {
208
+ resolveObject = resolve;
209
+ rejectObject = reject;
210
+ });
211
+ const rawTextPromise = new Promise((resolve) => {
212
+ resolveRaw = resolve;
213
+ });
214
+ const usagePromise = new Promise((resolve) => {
215
+ resolveUsage = resolve;
216
+ });
217
+ // Shared state for background consumer and partial stream
218
+ const partialValues = [];
219
+ let streamComplete = false;
220
+ let streamError = null;
221
+ const waiters = [];
222
+ // Background consumer - ensures object resolves even if partialObjectStream is not iterated
223
+ const backgroundConsumer = (async () => {
224
+ try {
225
+ for await (const chunk of response) {
226
+ const delta = chunk.choices?.[0]?.delta?.content ?? '';
227
+ if (delta) {
228
+ rawText += delta;
229
+ parser.append(delta);
230
+ // Try to parse partial object
231
+ const result = parser.parsePartial();
232
+ if (result.value !== null) {
233
+ partialValues.push(result.value);
234
+ // Wake up any waiting iterators
235
+ while (waiters.length > 0) {
236
+ waiters.shift().resolve(false);
237
+ }
238
+ }
239
+ }
240
+ // Capture usage from final chunk
241
+ if (chunk.usage) {
242
+ usage = chunk.usage;
243
+ }
244
+ }
245
+ // Stream complete - cache success
246
+ capabilityCache.set(client, model, 'stream_json_schema', true);
247
+ // Resolve final values
248
+ streamComplete = true;
249
+ resolveRaw(rawText);
250
+ resolveUsage(usage);
251
+ // Validate final object
252
+ try {
253
+ const parseResult = schema.safeParse(JSON.parse(rawText));
254
+ if (parseResult.success) {
255
+ resolveObject(parseResult.data);
256
+ }
257
+ else {
258
+ const validationErrors = parseResult.error.errors.map(e => ({
259
+ path: e.path.map(String),
260
+ message: e.message,
261
+ }));
262
+ const error = new StructuredOutputError('Validation failed', rawText, validationErrors);
263
+ streamError = error; // Ensure partialObjectStream also throws
264
+ rejectObject(error);
265
+ }
266
+ }
267
+ catch (e) {
268
+ const error = e instanceof Error ? e : new Error(String(e));
269
+ streamError = error; // Ensure partialObjectStream also throws
270
+ rejectObject(error);
271
+ }
272
+ // Wake up any waiting iterators
273
+ while (waiters.length > 0) {
274
+ waiters.shift().resolve(true);
275
+ }
276
+ }
277
+ catch (error) {
278
+ streamError = error instanceof Error ? error : new Error(String(error));
279
+ streamComplete = true;
280
+ resolveRaw(rawText);
281
+ resolveUsage(usage);
282
+ rejectObject(streamError);
283
+ // Wake up any waiting iterators
284
+ while (waiters.length > 0) {
285
+ waiters.shift().resolve(true);
286
+ }
287
+ }
288
+ })();
289
+ // Create async generator for partial objects (consumes from shared state)
290
+ async function* generatePartials() {
291
+ let yieldedCount = 0;
292
+ while (true) {
293
+ // Yield any new partial values
294
+ while (yieldedCount < partialValues.length) {
295
+ yield partialValues[yieldedCount++];
296
+ }
297
+ // Check if stream is complete
298
+ if (streamComplete) {
299
+ if (streamError)
300
+ throw streamError;
301
+ break;
302
+ }
303
+ // Wait for more data
304
+ await new Promise((resolve) => {
305
+ waiters.push({ resolve });
306
+ });
307
+ }
308
+ }
309
+ return {
310
+ partialObjectStream: generatePartials(),
311
+ object: objectPromise,
312
+ rawText: rawTextPromise,
313
+ usage: usagePromise,
314
+ streamed: true,
315
+ };
316
+ }
317
+ /**
318
+ * Create fallback result using non-streaming generateObject.
319
+ */
320
+ async function createFallbackResult(options) {
321
+ // Use generateObject (non-streaming)
322
+ const result = await generateObject({
323
+ client: options.client,
324
+ model: options.model,
325
+ schema: options.schema,
326
+ prompt: options.prompt,
327
+ messages: options.messages,
328
+ system: options.system,
329
+ temperature: options.temperature,
330
+ topP: options.topP,
331
+ maxTokens: options.maxTokens,
332
+ mode: 'strict',
333
+ abortSignal: options.abortSignal,
334
+ });
335
+ // Create single-yield async iterable
336
+ async function* singleYield() {
337
+ yield result.object;
338
+ }
339
+ return {
340
+ partialObjectStream: singleYield(),
341
+ object: Promise.resolve(result.object),
342
+ rawText: Promise.resolve(result.raw),
343
+ usage: Promise.resolve(result.usage),
344
+ streamed: false,
345
+ };
346
+ }
347
+ //# sourceMappingURL=stream-object.js.map
@@ -0,0 +1,90 @@
1
+ /**
2
+ * Tool Approval - Unified approval mechanism for tool execution.
3
+ * Implements Human-in-the-Loop pattern with configurable policies.
4
+ */
5
+ import type { ToolCall } from '../lib/types';
6
+ import type { RegisteredTool, ToolSource } from '../lib/tool-registry';
7
+ /** Context passed to approval handler */
8
+ export interface ApprovalContext {
9
+ /** Tool call details */
10
+ toolCall: {
11
+ id: string;
12
+ function: {
13
+ name: string;
14
+ arguments: string;
15
+ };
16
+ };
17
+ /** Tool name */
18
+ toolName: string;
19
+ /** Tool description */
20
+ toolDescription: string;
21
+ /** Parsed arguments */
22
+ args: Record<string, unknown>;
23
+ /** Current message history */
24
+ messages: Array<{
25
+ role: string;
26
+ content: unknown;
27
+ }>;
28
+ }
29
+ /** Approval handler function */
30
+ export type ApprovalHandler = (context: ApprovalContext) => Promise<boolean>;
31
+ /** Approval configuration */
32
+ export interface ApprovalConfig {
33
+ /** Global approval handler */
34
+ onApprovalRequired?: ApprovalHandler;
35
+ /**
36
+ * Sources to auto-approve (skip approval).
37
+ * Supports 'type' (e.g., 'builtin') or 'type:namespace' (e.g., 'mcp:github')
38
+ */
39
+ autoApproveSources?: string[];
40
+ }
41
+ /** Tool with optional approval settings */
42
+ export interface ToolWithApproval {
43
+ /** Whether this tool requires approval before execution */
44
+ requiresApproval?: boolean;
45
+ /** Per-tool approval handler (overrides global) */
46
+ approvalHandler?: ApprovalHandler;
47
+ }
48
+ /** Approval result */
49
+ export interface ApprovalResult {
50
+ approved: boolean;
51
+ /** Rejection message if not approved */
52
+ rejectionMessage?: string;
53
+ }
54
+ /** Tool-like object for approval checking */
55
+ interface ToolForApproval {
56
+ name: string;
57
+ description?: string;
58
+ source?: ToolSource;
59
+ requiresApproval?: boolean;
60
+ approvalHandler?: ApprovalHandler;
61
+ }
62
+ /** Tool call-like object for approval checking */
63
+ interface ToolCallForApproval {
64
+ id: string;
65
+ function: {
66
+ name: string;
67
+ arguments: string;
68
+ };
69
+ }
70
+ /**
71
+ * Determine if approval is needed and execute handler if so.
72
+ */
73
+ export declare function checkApproval(tool: ToolForApproval, toolCall: ToolCallForApproval, args: Record<string, unknown>, messages: Array<{
74
+ role: string;
75
+ content: unknown;
76
+ }>, config?: ApprovalConfig): Promise<ApprovalResult>;
77
+ /**
78
+ * Execute a single tool with approval check.
79
+ * Returns result or rejection message.
80
+ */
81
+ export declare function executeToolWithApproval(tool: RegisteredTool & ToolWithApproval, toolCall: ToolCall, args: Record<string, unknown>, messages: Array<{
82
+ role: string;
83
+ content: unknown;
84
+ }>, config?: ApprovalConfig, abortSignal?: AbortSignal): Promise<{
85
+ result: string;
86
+ isError: boolean;
87
+ isRejected: boolean;
88
+ }>;
89
+ export {};
90
+ //# sourceMappingURL=tool-approval.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tool-approval.d.ts","sourceRoot":"","sources":["../../src/ai/tool-approval.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAC7C,OAAO,KAAK,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAMvE,yCAAyC;AACzC,MAAM,WAAW,eAAe;IAC5B,wBAAwB;IACxB,QAAQ,EAAE;QACN,EAAE,EAAE,MAAM,CAAC;QACX,QAAQ,EAAE;YACN,IAAI,EAAE,MAAM,CAAC;YACb,SAAS,EAAE,MAAM,CAAC;SACrB,CAAC;KACL,CAAC;IACF,gBAAgB;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,uBAAuB;IACvB,eAAe,EAAE,MAAM,CAAC;IACxB,uBAAuB;IACvB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC9B,8BAA8B;IAC9B,QAAQ,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC;CACvD;AAED,gCAAgC;AAChC,MAAM,MAAM,eAAe,GAAG,CAAC,OAAO,EAAE,eAAe,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;AAE7E,6BAA6B;AAC7B,MAAM,WAAW,cAAc;IAC3B,8BAA8B;IAC9B,kBAAkB,CAAC,EAAE,eAAe,CAAC;IACrC;;;OAGG;IACH,kBAAkB,CAAC,EAAE,MAAM,EAAE,CAAC;CACjC;AAED,2CAA2C;AAC3C,MAAM,WAAW,gBAAgB;IAC7B,2DAA2D;IAC3D,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,mDAAmD;IACnD,eAAe,CAAC,EAAE,eAAe,CAAC;CACrC;AAED,sBAAsB;AACtB,MAAM,WAAW,cAAc;IAC3B,QAAQ,EAAE,OAAO,CAAC;IAClB,wCAAwC;IACxC,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC7B;AA6BD,6CAA6C;AAC7C,UAAU,eAAe;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,CAAC,EAAE,UAAU,CAAC;IACpB,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,eAAe,CAAC,EAAE,eAAe,CAAC;CACrC;AAED,kDAAkD;AAClD,UAAU,mBAAmB;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE;QACN,IAAI,EAAE,MAAM,CAAC;QACb,SAAS,EAAE,MAAM,CAAC;KACrB,CAAC;CACL;AAED;;GAEG;AACH,wBAAsB,aAAa,CAC/B,IAAI,EAAE,eAAe,EACrB,QAAQ,EAAE,mBAAmB,EAC7B,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,QAAQ,EAAE,KAAK,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,OAAO,CAAA;CAAE,CAAC,EACnD,MAAM,CAAC,EAAE,cAAc,GACxB,OAAO,CAAC,cAAc,CAAC,CA+CzB;AAED;;;GAGG;AACH,wBAAsB,uBAAuB,CACzC,IAAI,EAAE,cAAc,GAAG,gBAAgB,EACvC,QAAQ,EAAE,QAAQ,EAClB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,QAAQ,EAAE,KAAK,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,OAAO,CAAA;CAAE,CAAC,EACnD,MAAM,CAAC,EAAE,cAAc,EACvB,WAAW,CAAC,EAAE,WAAW,GAC1B,OAAO,CAAC;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,OAAO,CAAC;IAAC,UAAU,EAAE,OAAO,CAAA;CAAE,CAAC,CAoDpE"}
@@ -0,0 +1,151 @@
1
+ "use strict";
2
+ /**
3
+ * Tool Approval - Unified approval mechanism for tool execution.
4
+ * Implements Human-in-the-Loop pattern with configurable policies.
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.checkApproval = checkApproval;
8
+ exports.executeToolWithApproval = executeToolWithApproval;
9
+ // ============================================================================
10
+ // Rejection Message
11
+ // ============================================================================
12
+ const REJECTION_MESSAGE = '[Approval Rejected] Tool execution was denied by user.';
13
+ // ============================================================================
14
+ // Approval Logic
15
+ // ============================================================================
16
+ /**
17
+ * Check if a tool source matches an auto-approve pattern.
18
+ */
19
+ function isSourceAutoApproved(source, patterns) {
20
+ for (const pattern of patterns) {
21
+ // Match full source (e.g., 'mcp:github')
22
+ if (pattern === `${source.type}:${source.namespace}`) {
23
+ return true;
24
+ }
25
+ // Match type only (e.g., 'builtin')
26
+ if (pattern === source.type) {
27
+ return true;
28
+ }
29
+ }
30
+ return false;
31
+ }
32
+ /**
33
+ * Determine if approval is needed and execute handler if so.
34
+ */
35
+ async function checkApproval(tool, toolCall, args, messages, config) {
36
+ // Tool doesn't require approval
37
+ if (!tool.requiresApproval) {
38
+ return { approved: true };
39
+ }
40
+ // Check auto-approve sources
41
+ if (config?.autoApproveSources && tool.source) {
42
+ if (isSourceAutoApproved(tool.source, config.autoApproveSources)) {
43
+ return { approved: true };
44
+ }
45
+ }
46
+ // Get handler (per-tool overrides global)
47
+ const handler = tool.approvalHandler ?? config?.onApprovalRequired;
48
+ // No handler configured but approval required - FAIL CLOSED
49
+ if (!handler) {
50
+ return {
51
+ approved: false,
52
+ rejectionMessage: '[Approval Required] No handler configured. Tool execution denied.',
53
+ };
54
+ }
55
+ // Execute handler
56
+ const context = {
57
+ toolCall,
58
+ toolName: tool.name,
59
+ toolDescription: tool.description ?? '',
60
+ args,
61
+ messages,
62
+ };
63
+ try {
64
+ const approved = await handler(context);
65
+ return {
66
+ approved,
67
+ rejectionMessage: approved ? undefined : REJECTION_MESSAGE,
68
+ };
69
+ }
70
+ catch (error) {
71
+ // Handler error = rejection
72
+ const errorMessage = error instanceof Error ? error.message : String(error);
73
+ return {
74
+ approved: false,
75
+ rejectionMessage: `[Approval Error] ${errorMessage}`,
76
+ };
77
+ }
78
+ }
79
+ /**
80
+ * Execute a single tool with approval check.
81
+ * Returns result or rejection message.
82
+ */
83
+ async function executeToolWithApproval(tool, toolCall, args, messages, config, abortSignal) {
84
+ // Check approval
85
+ const approvalResult = await checkApproval(tool, toolCall, args, messages, config);
86
+ if (!approvalResult.approved) {
87
+ return {
88
+ result: approvalResult.rejectionMessage ?? REJECTION_MESSAGE,
89
+ isError: false, // Not an error, just rejected
90
+ isRejected: true,
91
+ };
92
+ }
93
+ // Check abort
94
+ if (abortSignal?.aborted) {
95
+ return {
96
+ result: 'Execution cancelled',
97
+ isError: true,
98
+ isRejected: false,
99
+ };
100
+ }
101
+ // Execute tool
102
+ if (!tool.execute) {
103
+ return {
104
+ result: `Tool ${tool.name} has no execute function`,
105
+ isError: true,
106
+ isRejected: false,
107
+ };
108
+ }
109
+ try {
110
+ const execContext = {
111
+ toolCallId: toolCall.id,
112
+ messages: messages,
113
+ abortSignal,
114
+ };
115
+ const result = await tool.execute(args, execContext);
116
+ return {
117
+ result: serializeResult(result),
118
+ isError: false,
119
+ isRejected: false,
120
+ };
121
+ }
122
+ catch (error) {
123
+ const errorMessage = error instanceof Error ? error.message : String(error);
124
+ return {
125
+ result: `Error: ${errorMessage}`,
126
+ isError: true,
127
+ isRejected: false,
128
+ };
129
+ }
130
+ }
131
+ // ============================================================================
132
+ // Helpers
133
+ // ============================================================================
134
+ /**
135
+ * Serialize tool result to string.
136
+ */
137
+ function serializeResult(result) {
138
+ if (result === undefined || result === null) {
139
+ return '';
140
+ }
141
+ if (typeof result === 'string') {
142
+ return result;
143
+ }
144
+ try {
145
+ return JSON.stringify(result, null, 2);
146
+ }
147
+ catch {
148
+ return String(result);
149
+ }
150
+ }
151
+ //# sourceMappingURL=tool-approval.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tool-approval.js","sourceRoot":"","sources":["../../src/ai/tool-approval.ts"],"names":[],"mappings":"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"}