@pikku/assistant-ui 0.12.0 → 0.12.1

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.
@@ -1,8 +1,385 @@
1
1
  "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ var __await = (this && this.__await) || function (v) { return this instanceof __await ? (this.v = v, this) : new __await(v); }
12
+ var __asyncGenerator = (this && this.__asyncGenerator) || function (thisArg, _arguments, generator) {
13
+ if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
14
+ var g = generator.apply(thisArg, _arguments || []), i, q = [];
15
+ return i = Object.create((typeof AsyncIterator === "function" ? AsyncIterator : Object).prototype), verb("next"), verb("throw"), verb("return", awaitReturn), i[Symbol.asyncIterator] = function () { return this; }, i;
16
+ function awaitReturn(f) { return function (v) { return Promise.resolve(v).then(f, reject); }; }
17
+ function verb(n, f) { if (g[n]) { i[n] = function (v) { return new Promise(function (a, b) { q.push([n, v, a, b]) > 1 || resume(n, v); }); }; if (f) i[n] = f(i[n]); } }
18
+ function resume(n, v) { try { step(g[n](v)); } catch (e) { settle(q[0][3], e); } }
19
+ function step(r) { r.value instanceof __await ? Promise.resolve(r.value.v).then(fulfill, reject) : settle(q[0][2], r); }
20
+ function fulfill(value) { resume("next", value); }
21
+ function reject(value) { resume("throw", value); }
22
+ function settle(f, v) { if (f(v), q.shift(), q.length) resume(q[0][0], q[0][1]); }
23
+ };
24
+ var __asyncValues = (this && this.__asyncValues) || function (o) {
25
+ if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
26
+ var m = o[Symbol.asyncIterator], i;
27
+ return m ? m.call(o) : (o = typeof __values === "function" ? __values(o) : o[Symbol.iterator](), i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i);
28
+ function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; }
29
+ function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); }
30
+ };
2
31
  Object.defineProperty(exports, "__esModule", { value: true });
32
+ exports.convertDbMessages = exports.usePikkuApproval = exports.PikkuApprovalContext = void 0;
33
+ exports.isDeniedResult = isDeniedResult;
34
+ exports.resolvePikkuToolStatus = resolvePikkuToolStatus;
3
35
  exports.usePikkuAgentRuntime = usePikkuAgentRuntime;
36
+ exports.usePikkuAgentNonStreamingRuntime = usePikkuAgentNonStreamingRuntime;
4
37
  const react_1 = require("react");
5
- const react_data_stream_1 = require("@assistant-ui/react-data-stream");
38
+ const react_2 = require("@assistant-ui/react");
39
+ exports.PikkuApprovalContext = (0, react_1.createContext)({
40
+ pendingApprovals: [],
41
+ handleApproval: () => { },
42
+ });
43
+ const usePikkuApproval = () => (0, react_1.useContext)(exports.PikkuApprovalContext);
44
+ exports.usePikkuApproval = usePikkuApproval;
45
+ function parseSSEStream(reader) {
46
+ return __asyncGenerator(this, arguments, function* parseSSEStream_1() {
47
+ const decoder = new TextDecoder();
48
+ let buffer = '';
49
+ while (true) {
50
+ const { done, value } = yield __await(reader.read());
51
+ if (done)
52
+ break;
53
+ buffer += decoder.decode(value, { stream: true });
54
+ const lines = buffer.split('\n');
55
+ buffer = lines.pop();
56
+ for (const line of lines) {
57
+ if (line.startsWith('data: ')) {
58
+ const data = line.slice(6);
59
+ if (data) {
60
+ try {
61
+ yield yield __await(JSON.parse(data));
62
+ }
63
+ catch (_a) {
64
+ // skip unparseable lines
65
+ }
66
+ }
67
+ }
68
+ }
69
+ }
70
+ });
71
+ }
72
+ /**
73
+ * Shared helper: consume an SSE stream and populate text/toolCalls.
74
+ * Returns an array of PendingApprovals when the stream requests them, or empty when done.
75
+ */
76
+ function processStream(reader, text, toolCalls, yieldContent, onFinish) {
77
+ return __awaiter(this, void 0, void 0, function* () {
78
+ var _a, e_1, _b, _c;
79
+ const pendingApprovals = [];
80
+ try {
81
+ for (var _d = true, _e = __asyncValues(parseSSEStream(reader)), _f; _f = yield _e.next(), _a = _f.done, !_a; _d = true) {
82
+ _c = _f.value;
83
+ _d = false;
84
+ const event = _c;
85
+ switch (event.type) {
86
+ case 'text-delta':
87
+ text.value += event.text;
88
+ break;
89
+ case 'tool-call': {
90
+ let parsedArgs = event.args;
91
+ if (typeof event.args === 'string') {
92
+ try {
93
+ parsedArgs = JSON.parse(event.args);
94
+ }
95
+ catch (_g) {
96
+ parsedArgs = {};
97
+ }
98
+ }
99
+ toolCalls.push({
100
+ type: 'tool-call',
101
+ toolCallId: event.toolCallId,
102
+ toolName: event.toolName,
103
+ args: parsedArgs,
104
+ });
105
+ break;
106
+ }
107
+ case 'tool-result': {
108
+ // Skip tool-results that contain __approvalRequired
109
+ const resultObj = typeof event.result === 'object' ? event.result : null;
110
+ if (resultObj && '__approvalRequired' in resultObj) {
111
+ break;
112
+ }
113
+ const tc = toolCalls.find((t) => t.toolCallId === event.toolCallId);
114
+ if (tc) {
115
+ tc.result =
116
+ typeof event.result === 'string'
117
+ ? event.result
118
+ : JSON.stringify(event.result);
119
+ if (event.isError) {
120
+ tc.isError = true;
121
+ }
122
+ }
123
+ break;
124
+ }
125
+ case 'approval-request':
126
+ pendingApprovals.push({
127
+ toolCallId: event.toolCallId,
128
+ toolName: event.toolName,
129
+ args: event.args,
130
+ reason: event.reason,
131
+ runId: event.runId,
132
+ });
133
+ break;
134
+ case 'error':
135
+ text.value += `\n\nError: ${event.message || event.errorText || 'Unknown error'}`;
136
+ break;
137
+ case 'done':
138
+ onFinish === null || onFinish === void 0 ? void 0 : onFinish();
139
+ continue;
140
+ }
141
+ yieldContent();
142
+ }
143
+ }
144
+ catch (e_1_1) { e_1 = { error: e_1_1 }; }
145
+ finally {
146
+ try {
147
+ if (!_d && !_a && (_b = _e.return)) yield _b.call(_e);
148
+ }
149
+ finally { if (e_1) throw e_1.error; }
150
+ }
151
+ return pendingApprovals;
152
+ });
153
+ }
154
+ function isDeniedResult(result) {
155
+ if (result == null)
156
+ return false;
157
+ try {
158
+ const parsed = typeof result === 'string' ? JSON.parse(result) : result;
159
+ return parsed && typeof parsed === 'object' && parsed.approved === false;
160
+ }
161
+ catch (_a) {
162
+ return false;
163
+ }
164
+ }
165
+ function resolvePikkuToolStatus(status, result) {
166
+ if (status.type === 'running')
167
+ return { type: 'running' };
168
+ if (status.type === 'requires-action')
169
+ return { type: 'requires-action' };
170
+ if (isDeniedResult(result))
171
+ return { type: 'denied' };
172
+ if (typeof result === 'string' && result.startsWith('Error:'))
173
+ return { type: 'error' };
174
+ return { type: 'completed' };
175
+ }
176
+ function buildContent(text, toolCalls) {
177
+ const content = [];
178
+ if (text.value)
179
+ content.push({ type: 'text', text: text.value });
180
+ content.push(...toolCalls);
181
+ return content;
182
+ }
183
+ function createPikkuStreamingAdapter(optionsRef, pendingApprovalsRef, approvalDecisionsRef, setPendingApprovalsRef, onFinishRef) {
184
+ return {
185
+ run(_a) {
186
+ return __asyncGenerator(this, arguments, function* run_1({ messages, abortSignal }) {
187
+ var _b, _c, _d, _e;
188
+ const opts = optionsRef.current;
189
+ // Check if this run() is a continuation after approval decisions.
190
+ // assistant-ui calls run() again after addResult provides tool results for ALL tool calls.
191
+ const pendingApprovals = pendingApprovalsRef.current;
192
+ if (pendingApprovals.length > 0) {
193
+ // Read the decisions accumulated by handleApproval() from click handlers.
194
+ const decisions = approvalDecisionsRef.current;
195
+ approvalDecisionsRef.current = [];
196
+ if (decisions.length === 0) {
197
+ // No decisions set — shouldn't happen if composer is disabled.
198
+ return yield __await(void 0);
199
+ }
200
+ // Clear pending approvals state
201
+ pendingApprovalsRef.current = [];
202
+ setPendingApprovalsRef.current([]);
203
+ // Send /resume for each decision sequentially.
204
+ // All but the last resume will return quickly (just tool-result + done).
205
+ // The last resume triggers continuation (next LLM step).
206
+ let lastText = { value: '' };
207
+ let lastToolCalls = [];
208
+ let nextApprovals = [];
209
+ for (let i = 0; i < decisions.length; i++) {
210
+ const decision = decisions[i];
211
+ // Find the runId from the matching pending approval
212
+ const matchingApproval = pendingApprovals.find((p) => p.toolCallId === decision.toolCallId);
213
+ const runId = (_b = matchingApproval === null || matchingApproval === void 0 ? void 0 : matchingApproval.runId) !== null && _b !== void 0 ? _b : (_c = pendingApprovals[0]) === null || _c === void 0 ? void 0 : _c.runId;
214
+ const resumeResponse = yield __await(fetch(`${opts.api}/${opts.agentName}/resume`, {
215
+ method: 'POST',
216
+ headers: Object.assign({ 'Content-Type': 'application/json' }, opts.headers),
217
+ body: JSON.stringify({
218
+ runId,
219
+ toolCallId: decision.toolCallId,
220
+ approved: decision.approved,
221
+ }),
222
+ signal: abortSignal,
223
+ credentials: opts.credentials,
224
+ }));
225
+ if (!resumeResponse.ok || !resumeResponse.body) {
226
+ const errorText = resumeResponse.body
227
+ ? yield __await(resumeResponse.text().catch(() => ''))
228
+ : '';
229
+ throw new Error(`Resume failed: ${resumeResponse.status}${errorText ? ` - ${errorText}` : ''}`);
230
+ }
231
+ const text = { value: '' };
232
+ const toolCalls = [];
233
+ const reader = resumeResponse.body.getReader();
234
+ const streamApprovals = yield __await(processStream(reader, text, toolCalls, () => { }, i === decisions.length - 1
235
+ ? ((_d = onFinishRef.current) !== null && _d !== void 0 ? _d : undefined)
236
+ : undefined)
237
+ // Keep the last resume's output (it has continuation content)
238
+ );
239
+ // Keep the last resume's output (it has continuation content)
240
+ lastText = text;
241
+ lastToolCalls = toolCalls;
242
+ if (streamApprovals.length > 0) {
243
+ nextApprovals = streamApprovals;
244
+ }
245
+ }
246
+ // Build content from the last resume's output
247
+ const content = buildContent(lastText, lastToolCalls);
248
+ if (nextApprovals.length > 0) {
249
+ // More approvals from continuation — show them
250
+ pendingApprovalsRef.current = nextApprovals;
251
+ setPendingApprovalsRef.current(nextApprovals);
252
+ // Add approval tool calls to content
253
+ for (const approval of nextApprovals) {
254
+ const approvalToolCall = {
255
+ type: 'tool-call',
256
+ toolCallId: approval.toolCallId,
257
+ toolName: approval.toolName,
258
+ args: Object.assign(Object.assign({}, (typeof approval.args === 'object' && approval.args !== null
259
+ ? approval.args
260
+ : {})), (approval.reason
261
+ ? { __approvalReason: approval.reason }
262
+ : {})),
263
+ };
264
+ const idx = lastToolCalls.findIndex((tc) => tc.toolCallId === approval.toolCallId && !tc.result);
265
+ if (idx !== -1) {
266
+ lastToolCalls[idx] = approvalToolCall;
267
+ }
268
+ else {
269
+ lastToolCalls.push(approvalToolCall);
270
+ }
271
+ }
272
+ const updatedContent = buildContent(lastText, lastToolCalls);
273
+ yield yield __await({
274
+ content: updatedContent,
275
+ status: {
276
+ type: 'requires-action',
277
+ reason: 'tool-calls',
278
+ },
279
+ });
280
+ }
281
+ else if (content.length > 0) {
282
+ yield yield __await({ content });
283
+ }
284
+ return yield __await(void 0);
285
+ }
286
+ // Normal flow: new user message → stream
287
+ const lastUserMsg = [...messages].reverse().find((m) => m.role === 'user');
288
+ if (!lastUserMsg)
289
+ return yield __await(void 0);
290
+ let messageText = '';
291
+ if (lastUserMsg.content) {
292
+ for (const part of lastUserMsg.content) {
293
+ if ('text' in part && part.type === 'text') {
294
+ messageText += part.text;
295
+ }
296
+ }
297
+ }
298
+ const response = yield __await(fetch(`${opts.api}/${opts.agentName}/stream`, {
299
+ method: 'POST',
300
+ headers: Object.assign({ 'Content-Type': 'application/json' }, opts.headers),
301
+ body: JSON.stringify({
302
+ agentName: opts.agentName,
303
+ message: messageText,
304
+ threadId: opts.threadId,
305
+ resourceId: opts.resourceId,
306
+ model: opts.model,
307
+ temperature: opts.temperature,
308
+ }),
309
+ signal: abortSignal,
310
+ credentials: opts.credentials,
311
+ }));
312
+ if (!response.ok || !response.body) {
313
+ throw new Error(`Agent stream failed: ${response.status}`);
314
+ }
315
+ const text = { value: '' };
316
+ const toolCalls = [];
317
+ let pendingContent = null;
318
+ const yieldContent = () => {
319
+ const content = buildContent(text, toolCalls);
320
+ if (content.length > 0)
321
+ pendingContent = content;
322
+ };
323
+ const reader = response.body.getReader();
324
+ const approvals = yield __await(processStream(reader, text, toolCalls, yieldContent, (_e = onFinishRef.current) !== null && _e !== void 0 ? _e : undefined));
325
+ if (approvals.length === 0) {
326
+ // No approval needed — yield final content and done
327
+ if (pendingContent) {
328
+ yield yield __await({ content: pendingContent });
329
+ }
330
+ return yield __await(void 0);
331
+ }
332
+ // Approvals requested: store them for the next run() call
333
+ pendingApprovalsRef.current = approvals;
334
+ setPendingApprovalsRef.current(approvals);
335
+ // Each approval tool call needs to be shown without a result
336
+ // so assistant-ui renders them as requires-action
337
+ for (const approval of approvals) {
338
+ const approvalToolCall = {
339
+ type: 'tool-call',
340
+ toolCallId: approval.toolCallId,
341
+ toolName: approval.toolName,
342
+ args: Object.assign(Object.assign({}, (typeof approval.args === 'object' && approval.args !== null
343
+ ? approval.args
344
+ : {})), (approval.reason ? { __approvalReason: approval.reason } : {})),
345
+ };
346
+ // Replace the existing tool call (if any) with the approval version
347
+ const parentIdx = toolCalls.findIndex((tc) => tc.toolCallId === approval.toolCallId && !tc.result);
348
+ if (parentIdx !== -1) {
349
+ toolCalls[parentIdx] = approvalToolCall;
350
+ }
351
+ else {
352
+ toolCalls.push(approvalToolCall);
353
+ }
354
+ }
355
+ // Remove any forwarded sub-agent tool calls that duplicate approval tool names
356
+ const approvalToolCallIds = new Set(approvals.map((a) => a.toolCallId));
357
+ const approvalToolNames = new Set(approvals.map((a) => a.toolName));
358
+ for (let i = toolCalls.length - 1; i >= 0; i--) {
359
+ if (approvalToolNames.has(toolCalls[i].toolName) &&
360
+ !approvalToolCallIds.has(toolCalls[i].toolCallId)) {
361
+ toolCalls.splice(i, 1);
362
+ }
363
+ }
364
+ const content = buildContent(text, toolCalls);
365
+ yield yield __await({
366
+ content,
367
+ status: {
368
+ type: 'requires-action',
369
+ reason: 'tool-calls',
370
+ },
371
+ }
372
+ // Generator returns here. assistant-ui will show the approval UI for each tool call.
373
+ // When the user clicks Approve/Deny on ALL tools → handleApproval accumulates decisions →
374
+ // addResult for each → run() is called again when all have results.
375
+ );
376
+ // Generator returns here. assistant-ui will show the approval UI for each tool call.
377
+ // When the user clicks Approve/Deny on ALL tools → handleApproval accumulates decisions →
378
+ // addResult for each → run() is called again when all have results.
379
+ });
380
+ },
381
+ };
382
+ }
6
383
  const convertDbMessages = (dbMessages) => {
7
384
  const result = [];
8
385
  let currentAssistant = null;
@@ -96,68 +473,217 @@ const convertDbMessages = (dbMessages) => {
96
473
  }
97
474
  return result;
98
475
  };
476
+ exports.convertDbMessages = convertDbMessages;
99
477
  function usePikkuAgentRuntime(options) {
100
- const { api, threadId = null, initialMessages: rawInitialMessages, onThreadCreated, onFinish, onApprovalRequest, credentials, headers, } = options;
101
- const threadIdRef = (0, react_1.useRef)(threadId);
102
- threadIdRef.current = threadId;
103
- const justCreatedThreadRef = (0, react_1.useRef)(false);
104
- const bodyFn = (0, react_1.useCallback)(() => {
105
- let currentThreadId = threadIdRef.current;
106
- if (!currentThreadId) {
107
- currentThreadId = crypto.randomUUID();
108
- justCreatedThreadRef.current = true;
109
- onThreadCreated === null || onThreadCreated === void 0 ? void 0 : onThreadCreated(currentThreadId);
110
- }
111
- return { threadId: currentThreadId };
112
- }, [onThreadCreated]);
113
- const onData = (0, react_1.useCallback)((event) => {
114
- if (event.name === 'approval-request') {
115
- const approval = event.data;
116
- onApprovalRequest === null || onApprovalRequest === void 0 ? void 0 : onApprovalRequest({
117
- toolCallId: approval.toolCallId,
478
+ const [pendingApprovals, setPendingApprovals] = (0, react_1.useState)([]);
479
+ const optionsRef = (0, react_1.useRef)(options);
480
+ optionsRef.current = options;
481
+ const onFinishRef = (0, react_1.useRef)(options.onFinish);
482
+ onFinishRef.current = options.onFinish;
483
+ const pendingApprovalsRef = (0, react_1.useRef)([]);
484
+ const approvalDecisionsRef = (0, react_1.useRef)([]);
485
+ const setPendingApprovalsRef = (0, react_1.useRef)(setPendingApprovals);
486
+ setPendingApprovalsRef.current = setPendingApprovals;
487
+ const adapter = (0, react_1.useMemo)(() => createPikkuStreamingAdapter(optionsRef, pendingApprovalsRef, approvalDecisionsRef, setPendingApprovalsRef, onFinishRef), []);
488
+ const initialMessages = (0, react_1.useMemo)(() => options.initialMessages
489
+ ? (0, exports.convertDbMessages)(options.initialMessages)
490
+ : undefined, [options.initialMessages]);
491
+ const runtime = (0, react_2.useLocalRuntime)(adapter, { initialMessages });
492
+ // handleApproval is called from the Approve/Deny button click handler.
493
+ // It accumulates the decision in the ref. assistant-ui will call run()
494
+ // when ALL tool calls have results (via addResult).
495
+ const handleApproval = (0, react_1.useCallback)((toolCallId, approved) => {
496
+ approvalDecisionsRef.current.push({ toolCallId, approved });
497
+ }, []);
498
+ const isAwaitingApproval = pendingApprovals.length > 0;
499
+ return {
500
+ runtime,
501
+ pendingApprovals,
502
+ isAwaitingApproval,
503
+ handleApproval,
504
+ };
505
+ }
506
+ function createPikkuNonStreamingAdapter(optionsRef, pendingApprovalsRef, approvalDecisionsRef, setPendingApprovalsRef, onFinishRef) {
507
+ return {
508
+ run(_a) {
509
+ return __asyncGenerator(this, arguments, function* run_2({ messages, abortSignal }) {
510
+ var _b, _c, _d, _e, _f;
511
+ const opts = optionsRef.current;
512
+ // Continuation after approval decisions
513
+ const pendingApprovals = pendingApprovalsRef.current;
514
+ if (pendingApprovals.length > 0) {
515
+ const decisions = approvalDecisionsRef.current;
516
+ approvalDecisionsRef.current = [];
517
+ if (decisions.length === 0)
518
+ return yield __await(void 0);
519
+ pendingApprovalsRef.current = [];
520
+ setPendingApprovalsRef.current([]);
521
+ // Resume uses SSE (same as streaming mode)
522
+ let lastText = { value: '' };
523
+ let lastToolCalls = [];
524
+ let nextApprovals = [];
525
+ for (let i = 0; i < decisions.length; i++) {
526
+ const decision = decisions[i];
527
+ const matchingApproval = pendingApprovals.find((p) => p.toolCallId === decision.toolCallId);
528
+ const runId = (_b = matchingApproval === null || matchingApproval === void 0 ? void 0 : matchingApproval.runId) !== null && _b !== void 0 ? _b : (_c = pendingApprovals[0]) === null || _c === void 0 ? void 0 : _c.runId;
529
+ const resumeResponse = yield __await(fetch(`${opts.api}/${opts.agentName}/resume`, {
530
+ method: 'POST',
531
+ headers: Object.assign({ 'Content-Type': 'application/json' }, opts.headers),
532
+ body: JSON.stringify({
533
+ runId,
534
+ toolCallId: decision.toolCallId,
535
+ approved: decision.approved,
536
+ }),
537
+ signal: abortSignal,
538
+ credentials: opts.credentials,
539
+ }));
540
+ if (!resumeResponse.ok || !resumeResponse.body) {
541
+ const errorText = resumeResponse.body
542
+ ? yield __await(resumeResponse.text().catch(() => ''))
543
+ : '';
544
+ throw new Error(`Resume failed: ${resumeResponse.status}${errorText ? ` - ${errorText}` : ''}`);
545
+ }
546
+ const text = { value: '' };
547
+ const toolCalls = [];
548
+ const reader = resumeResponse.body.getReader();
549
+ const streamApprovals = yield __await(processStream(reader, text, toolCalls, () => { }, i === decisions.length - 1
550
+ ? ((_d = onFinishRef.current) !== null && _d !== void 0 ? _d : undefined)
551
+ : undefined));
552
+ lastText = text;
553
+ lastToolCalls = toolCalls;
554
+ if (streamApprovals.length > 0) {
555
+ nextApprovals = streamApprovals;
556
+ }
557
+ }
558
+ const content = buildContent(lastText, lastToolCalls);
559
+ if (nextApprovals.length > 0) {
560
+ pendingApprovalsRef.current = nextApprovals;
561
+ setPendingApprovalsRef.current(nextApprovals);
562
+ for (const approval of nextApprovals) {
563
+ const approvalToolCall = {
564
+ type: 'tool-call',
565
+ toolCallId: approval.toolCallId,
566
+ toolName: approval.toolName,
567
+ args: Object.assign(Object.assign({}, (typeof approval.args === 'object' && approval.args !== null
568
+ ? approval.args
569
+ : {})), (approval.reason
570
+ ? { __approvalReason: approval.reason }
571
+ : {})),
572
+ };
573
+ const idx = lastToolCalls.findIndex((tc) => tc.toolCallId === approval.toolCallId && !tc.result);
574
+ if (idx !== -1) {
575
+ lastToolCalls[idx] = approvalToolCall;
576
+ }
577
+ else {
578
+ lastToolCalls.push(approvalToolCall);
579
+ }
580
+ }
581
+ const updatedContent = buildContent(lastText, lastToolCalls);
582
+ yield yield __await({
583
+ content: updatedContent,
584
+ status: {
585
+ type: 'requires-action',
586
+ reason: 'tool-calls',
587
+ },
588
+ });
589
+ }
590
+ else if (content.length > 0) {
591
+ yield yield __await({ content });
592
+ }
593
+ return yield __await(void 0);
594
+ }
595
+ // Normal flow: new user message → non-streaming POST
596
+ const lastUserMsg = [...messages].reverse().find((m) => m.role === 'user');
597
+ if (!lastUserMsg)
598
+ return yield __await(void 0);
599
+ let messageText = '';
600
+ if (lastUserMsg.content) {
601
+ for (const part of lastUserMsg.content) {
602
+ if ('text' in part && part.type === 'text') {
603
+ messageText += part.text;
604
+ }
605
+ }
606
+ }
607
+ const response = yield __await(fetch(`${opts.api}/${opts.agentName}`, {
608
+ method: 'POST',
609
+ headers: Object.assign({ 'Content-Type': 'application/json' }, opts.headers),
610
+ body: JSON.stringify({
611
+ agentName: opts.agentName,
612
+ message: messageText,
613
+ threadId: opts.threadId,
614
+ resourceId: opts.resourceId,
615
+ model: opts.model,
616
+ temperature: opts.temperature,
617
+ }),
618
+ signal: abortSignal,
619
+ credentials: opts.credentials,
620
+ }));
621
+ if (!response.ok) {
622
+ throw new Error(`Agent run failed: ${response.status}`);
623
+ }
624
+ const json = yield __await(response.json());
625
+ if (json.status === 'suspended' && ((_e = json.pendingApprovals) === null || _e === void 0 ? void 0 : _e.length) > 0) {
626
+ const approvals = json.pendingApprovals;
627
+ pendingApprovalsRef.current = approvals;
628
+ setPendingApprovalsRef.current(approvals);
629
+ const toolCalls = approvals.map((approval) => ({
630
+ type: 'tool-call',
631
+ toolCallId: approval.toolCallId,
632
+ toolName: approval.toolName,
633
+ args: Object.assign(Object.assign({}, (typeof approval.args === 'object' && approval.args !== null
634
+ ? approval.args
635
+ : {})), (approval.reason ? { __approvalReason: approval.reason } : {})),
636
+ }));
637
+ const content = [];
638
+ if (json.result) {
639
+ content.push({ type: 'text', text: json.result });
640
+ }
641
+ content.push(...toolCalls);
642
+ yield yield __await({
643
+ content,
644
+ status: {
645
+ type: 'requires-action',
646
+ reason: 'tool-calls',
647
+ },
648
+ });
649
+ return yield __await(void 0);
650
+ }
651
+ // No approvals — yield complete content
652
+ (_f = onFinishRef.current) === null || _f === void 0 ? void 0 : _f.call(onFinishRef);
653
+ const content = [];
654
+ if (json.result) {
655
+ content.push({ type: 'text', text: String(json.result) });
656
+ }
657
+ if (content.length > 0) {
658
+ yield yield __await({ content });
659
+ }
118
660
  });
119
- }
120
- }, [onApprovalRequest]);
121
- const onFinishCb = (0, react_1.useCallback)(() => {
122
- onFinish === null || onFinish === void 0 ? void 0 : onFinish();
123
- }, [onFinish]);
124
- const initialMessages = (0, react_1.useMemo)(() => (rawInitialMessages ? convertDbMessages(rawInitialMessages) : []), [rawInitialMessages]);
125
- const runtime = (0, react_data_stream_1.useDataStreamRuntime)({
126
- api,
127
- protocol: 'ui-message-stream',
128
- body: bodyFn,
129
- onData,
130
- onFinish: onFinishCb,
131
- initialMessages,
132
- credentials,
133
- headers,
134
- });
135
- const prevThreadIdRef = (0, react_1.useRef)(threadId);
136
- const hasResetRef = (0, react_1.useRef)(false);
137
- (0, react_1.useEffect)(() => {
138
- if (prevThreadIdRef.current !== threadId) {
139
- prevThreadIdRef.current = threadId;
140
- if (justCreatedThreadRef.current) {
141
- justCreatedThreadRef.current = false;
142
- hasResetRef.current = true;
143
- return;
144
- }
145
- hasResetRef.current = false;
146
- if (rawInitialMessages) {
147
- ;
148
- runtime.thread.reset(convertDbMessages(rawInitialMessages));
149
- hasResetRef.current = true;
150
- }
151
- else {
152
- ;
153
- runtime.thread.reset([]);
154
- }
155
- }
156
- else if (!hasResetRef.current && rawInitialMessages) {
157
- ;
158
- runtime.thread.reset(convertDbMessages(rawInitialMessages));
159
- hasResetRef.current = true;
160
- }
161
- }, [threadId, rawInitialMessages, runtime]);
162
- return runtime;
661
+ },
662
+ };
663
+ }
664
+ function usePikkuAgentNonStreamingRuntime(options) {
665
+ const [pendingApprovals, setPendingApprovals] = (0, react_1.useState)([]);
666
+ const optionsRef = (0, react_1.useRef)(options);
667
+ optionsRef.current = options;
668
+ const onFinishRef = (0, react_1.useRef)(options.onFinish);
669
+ onFinishRef.current = options.onFinish;
670
+ const pendingApprovalsRef = (0, react_1.useRef)([]);
671
+ const approvalDecisionsRef = (0, react_1.useRef)([]);
672
+ const setPendingApprovalsRef = (0, react_1.useRef)(setPendingApprovals);
673
+ setPendingApprovalsRef.current = setPendingApprovals;
674
+ const adapter = (0, react_1.useMemo)(() => createPikkuNonStreamingAdapter(optionsRef, pendingApprovalsRef, approvalDecisionsRef, setPendingApprovalsRef, onFinishRef), []);
675
+ const initialMessages = (0, react_1.useMemo)(() => options.initialMessages
676
+ ? (0, exports.convertDbMessages)(options.initialMessages)
677
+ : undefined, [options.initialMessages]);
678
+ const runtime = (0, react_2.useLocalRuntime)(adapter, { initialMessages });
679
+ const handleApproval = (0, react_1.useCallback)((toolCallId, approved) => {
680
+ approvalDecisionsRef.current.push({ toolCallId, approved });
681
+ }, []);
682
+ const isAwaitingApproval = pendingApprovals.length > 0;
683
+ return {
684
+ runtime,
685
+ pendingApprovals,
686
+ isAwaitingApproval,
687
+ handleApproval,
688
+ };
163
689
  }