@pikku/assistant-ui 0.12.6 → 0.12.8

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.
@@ -8,187 +8,21 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
8
8
  step((generator = generator.apply(thisArg, _arguments || [])).next());
9
9
  });
10
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
- };
31
11
  Object.defineProperty(exports, "__esModule", { value: true });
32
12
  exports.convertDbMessages = exports.usePikkuApproval = exports.PikkuApprovalContext = void 0;
33
13
  exports.isDeniedResult = isDeniedResult;
34
14
  exports.resolvePikkuToolStatus = resolvePikkuToolStatus;
35
15
  exports.usePikkuAgentRuntime = usePikkuAgentRuntime;
36
- exports.usePikkuAgentNonStreamingRuntime = usePikkuAgentNonStreamingRuntime;
37
16
  const react_1 = require("react");
17
+ const client_1 = require("@ag-ui/client");
18
+ const react_ag_ui_1 = require("@assistant-ui/react-ag-ui");
38
19
  const react_2 = require("@assistant-ui/react");
39
20
  exports.PikkuApprovalContext = (0, react_1.createContext)({
40
21
  pendingApprovals: [],
41
- handleApproval: () => { },
22
+ handleApproval: () => __awaiter(void 0, void 0, void 0, function* () { return false; }),
42
23
  });
43
24
  const usePikkuApproval = () => (0, react_1.useContext)(exports.PikkuApprovalContext);
44
25
  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, structuredParts, 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 'generative-ui': {
126
- const nextPart = {
127
- type: 'generative-ui',
128
- spec: event.spec,
129
- };
130
- const existingIndex = structuredParts.findIndex((part) => part.type === 'generative-ui');
131
- if (existingIndex === -1)
132
- structuredParts.push(nextPart);
133
- else
134
- structuredParts[existingIndex] = nextPart;
135
- break;
136
- }
137
- case 'data': {
138
- const nextPart = {
139
- type: 'data',
140
- name: event.name,
141
- data: event.data,
142
- };
143
- const existingIndex = structuredParts.findIndex((part) => part.type === 'data' && part.name === event.name);
144
- if (existingIndex === -1)
145
- structuredParts.push(nextPart);
146
- else
147
- structuredParts[existingIndex] = nextPart;
148
- break;
149
- }
150
- case 'approval-request':
151
- pendingApprovals.push({
152
- toolCallId: event.toolCallId,
153
- toolName: event.toolName,
154
- args: event.args,
155
- reason: event.reason,
156
- runId: event.runId,
157
- type: 'approval-request',
158
- });
159
- break;
160
- case 'credential-request':
161
- pendingApprovals.push({
162
- toolCallId: event.toolCallId,
163
- toolName: event.toolName,
164
- args: event.args,
165
- runId: event.runId,
166
- type: 'credential-request',
167
- credentialName: event.credentialName,
168
- credentialType: event.credentialType,
169
- connectUrl: event.connectUrl,
170
- });
171
- break;
172
- case 'error':
173
- text.value += `\n\nError: ${event.message || event.errorText || 'Unknown error'}`;
174
- break;
175
- case 'done':
176
- onFinish === null || onFinish === void 0 ? void 0 : onFinish();
177
- continue;
178
- }
179
- yieldContent();
180
- }
181
- }
182
- catch (e_1_1) { e_1 = { error: e_1_1 }; }
183
- finally {
184
- try {
185
- if (!_d && !_a && (_b = _e.return)) yield _b.call(_e);
186
- }
187
- finally { if (e_1) throw e_1.error; }
188
- }
189
- return pendingApprovals;
190
- });
191
- }
192
26
  function isDeniedResult(result) {
193
27
  if (result == null)
194
28
  return false;
@@ -230,263 +64,6 @@ function resolvePikkuToolStatus(status, result) {
230
64
  return { type: 'error' };
231
65
  return { type: 'completed' };
232
66
  }
233
- function buildRichContent(text, structuredParts, toolCalls) {
234
- const content = [];
235
- if (text.value)
236
- content.push({ type: 'text', text: text.value });
237
- content.push(...structuredParts);
238
- content.push(...toolCalls);
239
- return content;
240
- }
241
- function buildContentFromAgentResult(result) {
242
- const content = [];
243
- if (typeof result === 'string') {
244
- if (result)
245
- content.push({ type: 'text', text: result });
246
- return content;
247
- }
248
- if (!result || typeof result !== 'object')
249
- return content;
250
- const record = result;
251
- if (typeof record.text === 'string' && record.text) {
252
- content.push({ type: 'text', text: record.text });
253
- }
254
- if (record.ui != null) {
255
- content.push({ type: 'generative-ui', spec: record.ui });
256
- }
257
- return content;
258
- }
259
- function createPikkuStreamingAdapter(optionsRef, pendingApprovalsRef, approvalDecisionsRef, setPendingApprovalsRef, onFinishRef) {
260
- return {
261
- run(_a) {
262
- return __asyncGenerator(this, arguments, function* run_1({ messages, abortSignal }) {
263
- var _b, _c, _d, _e;
264
- const opts = optionsRef.current;
265
- // Check if this run() is a continuation after approval decisions.
266
- // assistant-ui calls run() again after addResult provides tool results for ALL tool calls.
267
- const pendingApprovals = pendingApprovalsRef.current;
268
- if (pendingApprovals.length > 0) {
269
- // Read the decisions accumulated by handleApproval() from click handlers.
270
- const decisions = approvalDecisionsRef.current;
271
- approvalDecisionsRef.current = [];
272
- if (decisions.length === 0) {
273
- // No decisions set — shouldn't happen if composer is disabled.
274
- return yield __await(void 0);
275
- }
276
- // Clear pending approvals state
277
- pendingApprovalsRef.current = [];
278
- setPendingApprovalsRef.current([]);
279
- // Send /resume for each decision sequentially.
280
- // All but the last resume will return quickly (just tool-result + done).
281
- // The last resume triggers continuation (next LLM step).
282
- let lastText = { value: '' };
283
- let lastToolCalls = [];
284
- let lastStructuredParts = [];
285
- let nextApprovals = [];
286
- for (let i = 0; i < decisions.length; i++) {
287
- const decision = decisions[i];
288
- // Find the runId from the matching pending approval
289
- const matchingApproval = pendingApprovals.find((p) => p.toolCallId === decision.toolCallId);
290
- 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;
291
- const resumeResponse = yield __await(fetch(`${opts.api}/${opts.agentName}/resume`, {
292
- method: 'POST',
293
- headers: Object.assign({ 'Content-Type': 'application/json' }, opts.headers),
294
- body: JSON.stringify({
295
- runId,
296
- toolCallId: decision.toolCallId,
297
- approved: decision.approved,
298
- }),
299
- signal: abortSignal,
300
- credentials: opts.credentials,
301
- }));
302
- if (!resumeResponse.ok || !resumeResponse.body) {
303
- const errorText = resumeResponse.body
304
- ? yield __await(resumeResponse.text().catch(() => ''))
305
- : '';
306
- throw new Error(`Resume failed: ${resumeResponse.status}${errorText ? ` - ${errorText}` : ''}`);
307
- }
308
- const text = { value: '' };
309
- const toolCalls = [];
310
- const structuredParts = [];
311
- const reader = resumeResponse.body.getReader();
312
- const streamApprovals = yield __await(processStream(reader, text, toolCalls, structuredParts, () => { }, i === decisions.length - 1
313
- ? ((_d = onFinishRef.current) !== null && _d !== void 0 ? _d : undefined)
314
- : undefined)
315
- // Keep the last resume's output (it has continuation content)
316
- );
317
- // Keep the last resume's output (it has continuation content)
318
- lastText = text;
319
- lastToolCalls = toolCalls;
320
- lastStructuredParts = structuredParts;
321
- if (streamApprovals.length > 0) {
322
- nextApprovals = streamApprovals;
323
- }
324
- }
325
- // Build content from the last resume's output
326
- const content = buildRichContent(lastText, lastStructuredParts, lastToolCalls);
327
- if (nextApprovals.length > 0) {
328
- // More approvals from continuation — show them
329
- pendingApprovalsRef.current = nextApprovals;
330
- setPendingApprovalsRef.current(nextApprovals);
331
- // Add approval tool calls to content
332
- for (const approval of nextApprovals) {
333
- const approvalToolCall = {
334
- type: 'tool-call',
335
- toolCallId: approval.toolCallId,
336
- toolName: approval.toolName,
337
- args: Object.assign(Object.assign({}, (typeof approval.args === 'object' && approval.args !== null
338
- ? approval.args
339
- : {})), (approval.reason
340
- ? { __approvalReason: approval.reason }
341
- : {})),
342
- };
343
- const idx = lastToolCalls.findIndex((tc) => tc.toolCallId === approval.toolCallId && !tc.result);
344
- if (idx !== -1) {
345
- lastToolCalls[idx] = approvalToolCall;
346
- }
347
- else {
348
- lastToolCalls.push(approvalToolCall);
349
- }
350
- }
351
- const updatedContent = buildRichContent(lastText, lastStructuredParts, lastToolCalls);
352
- yield yield __await({
353
- content: updatedContent,
354
- status: {
355
- type: 'requires-action',
356
- reason: 'tool-calls',
357
- },
358
- });
359
- }
360
- else if (content.length > 0) {
361
- yield yield __await({ content });
362
- }
363
- return yield __await(void 0);
364
- }
365
- // Normal flow: new user message → stream
366
- const lastUserMsg = [...messages].reverse().find((m) => m.role === 'user');
367
- if (!lastUserMsg)
368
- return yield __await(void 0);
369
- let messageText = '';
370
- if (lastUserMsg.content) {
371
- for (const part of lastUserMsg.content) {
372
- if ('text' in part && part.type === 'text') {
373
- messageText += part.text;
374
- }
375
- }
376
- }
377
- let response;
378
- try {
379
- response = yield __await(fetch(`${opts.api}/${opts.agentName}/stream`, {
380
- method: 'POST',
381
- headers: Object.assign({ 'Content-Type': 'application/json' }, opts.headers),
382
- body: JSON.stringify(Object.assign({ agentName: opts.agentName, message: messageText, threadId: opts.threadId, resourceId: opts.resourceId, model: opts.model, temperature: opts.temperature }, (opts.context ? { context: opts.context } : {}))),
383
- signal: abortSignal,
384
- credentials: opts.credentials,
385
- }));
386
- }
387
- catch (e) {
388
- const msg = (e === null || e === void 0 ? void 0 : e.message) || 'Unknown error';
389
- let errorText = 'Failed to connect to the agent.';
390
- if (msg.includes('Failed to fetch') ||
391
- msg.includes('NetworkError') ||
392
- msg.includes('CORS')) {
393
- errorText =
394
- 'Unable to reach the agent server. The deployment may be down or the URL may be incorrect.';
395
- }
396
- else if (msg.includes('abort')) {
397
- return yield __await(void 0);
398
- }
399
- yield yield __await({ content: [{ type: 'text', text: `⚠️ ${errorText}` }] });
400
- return yield __await(void 0);
401
- }
402
- if (!response.ok || !response.body) {
403
- let errorText = `Request failed (${response.status}).`;
404
- if (response.status === 401 || response.status === 403) {
405
- errorText = 'Authentication error.';
406
- }
407
- else if (response.status === 404) {
408
- errorText =
409
- 'Agent not found — the agent may not be configured for this project.';
410
- }
411
- else if (response.status === 502 || response.status === 503) {
412
- errorText =
413
- 'The agent server is currently unavailable. Try again in a moment.';
414
- }
415
- else if (response.status === 429) {
416
- errorText = 'Rate limited — too many requests. Please wait a moment.';
417
- }
418
- yield yield __await({ content: [{ type: 'text', text: `⚠️ ${errorText}` }] });
419
- return yield __await(void 0);
420
- }
421
- const text = { value: '' };
422
- const toolCalls = [];
423
- const structuredParts = [];
424
- let pendingContent = null;
425
- const yieldContent = () => {
426
- const content = buildRichContent(text, structuredParts, toolCalls);
427
- if (content.length > 0)
428
- pendingContent = content;
429
- };
430
- const reader = response.body.getReader();
431
- const approvals = yield __await(processStream(reader, text, toolCalls, structuredParts, yieldContent, (_e = onFinishRef.current) !== null && _e !== void 0 ? _e : undefined));
432
- if (approvals.length === 0) {
433
- // No approval needed — yield final content and done
434
- if (pendingContent) {
435
- yield yield __await({ content: pendingContent });
436
- }
437
- return yield __await(void 0);
438
- }
439
- // Approvals requested: store them for the next run() call
440
- pendingApprovalsRef.current = approvals;
441
- setPendingApprovalsRef.current(approvals);
442
- // Each approval tool call needs to be shown without a result
443
- // so assistant-ui renders them as requires-action
444
- for (const approval of approvals) {
445
- const approvalToolCall = {
446
- type: 'tool-call',
447
- toolCallId: approval.toolCallId,
448
- toolName: approval.toolName,
449
- args: Object.assign(Object.assign({}, (typeof approval.args === 'object' && approval.args !== null
450
- ? approval.args
451
- : {})), (approval.reason ? { __approvalReason: approval.reason } : {})),
452
- };
453
- // Replace the existing tool call (if any) with the approval version
454
- const parentIdx = toolCalls.findIndex((tc) => tc.toolCallId === approval.toolCallId && !tc.result);
455
- if (parentIdx !== -1) {
456
- toolCalls[parentIdx] = approvalToolCall;
457
- }
458
- else {
459
- toolCalls.push(approvalToolCall);
460
- }
461
- }
462
- // Remove any forwarded sub-agent tool calls that duplicate approval tool names
463
- const approvalToolCallIds = new Set(approvals.map((a) => a.toolCallId));
464
- const approvalToolNames = new Set(approvals.map((a) => a.toolName));
465
- for (let i = toolCalls.length - 1; i >= 0; i--) {
466
- if (approvalToolNames.has(toolCalls[i].toolName) &&
467
- !approvalToolCallIds.has(toolCalls[i].toolCallId)) {
468
- toolCalls.splice(i, 1);
469
- }
470
- }
471
- const content = buildRichContent(text, structuredParts, toolCalls);
472
- yield yield __await({
473
- content,
474
- status: {
475
- type: 'requires-action',
476
- reason: 'tool-calls',
477
- },
478
- }
479
- // Generator returns here. assistant-ui will show the approval UI for each tool call.
480
- // When the user clicks Approve/Deny on ALL tools → handleApproval accumulates decisions →
481
- // addResult for each → run() is called again when all have results.
482
- );
483
- // Generator returns here. assistant-ui will show the approval UI for each tool call.
484
- // When the user clicks Approve/Deny on ALL tools → handleApproval accumulates decisions →
485
- // addResult for each → run() is called again when all have results.
486
- });
487
- },
488
- };
489
- }
490
67
  const convertDbMessages = (dbMessages) => {
491
68
  const result = [];
492
69
  let currentAssistant = null;
@@ -586,207 +163,175 @@ const convertDbMessages = (dbMessages) => {
586
163
  return result;
587
164
  };
588
165
  exports.convertDbMessages = convertDbMessages;
166
+ function extractLastUserMessage(messages) {
167
+ const lastUser = [...messages].reverse().find((m) => m.role === 'user');
168
+ if (!lastUser)
169
+ return '';
170
+ const content = lastUser.content;
171
+ if (typeof content === 'string')
172
+ return content;
173
+ if (Array.isArray(content)) {
174
+ return content
175
+ .filter((p) => p.type === 'text')
176
+ .map((p) => { var _a; return (_a = p.text) !== null && _a !== void 0 ? _a : ''; })
177
+ .join('');
178
+ }
179
+ return '';
180
+ }
181
+ class PikkuAgent extends client_1.HttpAgent {
182
+ constructor(opts) {
183
+ super({
184
+ url: `${opts.api}/${opts.agentName}/stream`,
185
+ threadId: opts.threadId,
186
+ });
187
+ this._pendingResume = null;
188
+ this._currentResume = null;
189
+ this.pikkuOpts = opts;
190
+ }
191
+ updateOpts(opts) {
192
+ this.pikkuOpts = opts;
193
+ }
194
+ queueResume(data) {
195
+ this._pendingResume = data;
196
+ }
197
+ run(input) {
198
+ const resume = this._pendingResume;
199
+ this._pendingResume = null;
200
+ this._currentResume = resume;
201
+ this.url = resume
202
+ ? `${this.pikkuOpts.api}/${this.pikkuOpts.agentName}/resume`
203
+ : `${this.pikkuOpts.api}/${this.pikkuOpts.agentName}/stream`;
204
+ return super.run(input);
205
+ }
206
+ requestInit(input) {
207
+ const base = super.requestInit(input);
208
+ const resume = this._currentResume;
209
+ const opts = this.pikkuOpts;
210
+ const headers = Object.assign({ 'Content-Type': 'application/json', Accept: 'text/event-stream' }, opts.headers);
211
+ if (resume) {
212
+ return Object.assign(Object.assign({}, base), { headers, credentials: opts.credentials, body: JSON.stringify(resume) });
213
+ }
214
+ return Object.assign(Object.assign({}, base), { headers, credentials: opts.credentials, body: JSON.stringify(Object.assign({ agentName: opts.agentName, message: extractLastUserMessage(input.messages), threadId: opts.threadId, resourceId: opts.resourceId, model: opts.model, temperature: opts.temperature }, (opts.context ? { context: opts.context } : {}))) });
215
+ }
216
+ }
589
217
  function usePikkuAgentRuntime(options) {
590
218
  const [pendingApprovals, setPendingApprovals] = (0, react_1.useState)([]);
591
- const optionsRef = (0, react_1.useRef)(options);
592
- optionsRef.current = options;
593
- const onFinishRef = (0, react_1.useRef)(options.onFinish);
594
- onFinishRef.current = options.onFinish;
219
+ // Authoritative pending list, mutated synchronously so two approval clicks
220
+ // in the same tick can't both read a stale "remaining" and skip the resume.
221
+ // State mirrors it purely for rendering.
595
222
  const pendingApprovalsRef = (0, react_1.useRef)([]);
596
- const approvalDecisionsRef = (0, react_1.useRef)([]);
597
- const setPendingApprovalsRef = (0, react_1.useRef)(setPendingApprovals);
598
- setPendingApprovalsRef.current = setPendingApprovals;
599
- const adapter = (0, react_1.useMemo)(() => createPikkuStreamingAdapter(optionsRef, pendingApprovalsRef, approvalDecisionsRef, setPendingApprovalsRef, onFinishRef), []);
600
- const initialMessages = (0, react_1.useMemo)(() => options.initialMessages
601
- ? (0, exports.convertDbMessages)(options.initialMessages)
602
- : undefined, [options.initialMessages]);
603
- const runtime = (0, react_2.useLocalRuntime)(adapter, { initialMessages });
604
- // handleApproval is called from the Approve/Deny button click handler.
605
- // It accumulates the decision in the ref. assistant-ui will call run()
606
- // when ALL tool calls have results (via addResult).
607
- const handleApproval = (0, react_1.useCallback)((toolCallId, approved) => {
608
- approvalDecisionsRef.current.push({ toolCallId, approved });
223
+ const commitPending = (0, react_1.useCallback)((next) => {
224
+ pendingApprovalsRef.current = next;
225
+ setPendingApprovals(next);
609
226
  }, []);
610
- const isAwaitingApproval = pendingApprovals.length > 0;
611
- return {
612
- runtime,
613
- pendingApprovals,
614
- isAwaitingApproval,
615
- handleApproval,
616
- };
617
- }
618
- function createPikkuNonStreamingAdapter(optionsRef, pendingApprovalsRef, approvalDecisionsRef, setPendingApprovalsRef, onFinishRef) {
619
- return {
620
- run(_a) {
621
- return __asyncGenerator(this, arguments, function* run_2({ messages, abortSignal }) {
622
- var _b, _c, _d, _e, _f;
623
- const opts = optionsRef.current;
624
- // Continuation after approval decisions
625
- const pendingApprovals = pendingApprovalsRef.current;
626
- if (pendingApprovals.length > 0) {
627
- const decisions = approvalDecisionsRef.current;
628
- approvalDecisionsRef.current = [];
629
- if (decisions.length === 0)
630
- return yield __await(void 0);
631
- pendingApprovalsRef.current = [];
632
- setPendingApprovalsRef.current([]);
633
- // Resume uses SSE (same as streaming mode)
634
- let lastText = { value: '' };
635
- let lastToolCalls = [];
636
- let lastStructuredParts = [];
637
- let nextApprovals = [];
638
- for (let i = 0; i < decisions.length; i++) {
639
- const decision = decisions[i];
640
- const matchingApproval = pendingApprovals.find((p) => p.toolCallId === decision.toolCallId);
641
- 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;
642
- const resumeResponse = yield __await(fetch(`${opts.api}/${opts.agentName}/resume`, {
643
- method: 'POST',
644
- headers: Object.assign({ 'Content-Type': 'application/json' }, opts.headers),
645
- body: JSON.stringify({
646
- runId,
647
- toolCallId: decision.toolCallId,
648
- approved: decision.approved,
649
- }),
650
- signal: abortSignal,
651
- credentials: opts.credentials,
652
- }));
653
- if (!resumeResponse.ok || !resumeResponse.body) {
654
- const errorText = resumeResponse.body
655
- ? yield __await(resumeResponse.text().catch(() => ''))
656
- : '';
657
- throw new Error(`Resume failed: ${resumeResponse.status}${errorText ? ` - ${errorText}` : ''}`);
658
- }
659
- const text = { value: '' };
660
- const toolCalls = [];
661
- const structuredParts = [];
662
- const reader = resumeResponse.body.getReader();
663
- const streamApprovals = yield __await(processStream(reader, text, toolCalls, structuredParts, () => { }, i === decisions.length - 1
664
- ? ((_d = onFinishRef.current) !== null && _d !== void 0 ? _d : undefined)
665
- : undefined));
666
- lastText = text;
667
- lastToolCalls = toolCalls;
668
- lastStructuredParts = structuredParts;
669
- if (streamApprovals.length > 0) {
670
- nextApprovals = streamApprovals;
671
- }
672
- }
673
- const content = buildRichContent(lastText, lastStructuredParts, lastToolCalls);
674
- if (nextApprovals.length > 0) {
675
- pendingApprovalsRef.current = nextApprovals;
676
- setPendingApprovalsRef.current(nextApprovals);
677
- for (const approval of nextApprovals) {
678
- const approvalToolCall = {
679
- type: 'tool-call',
680
- toolCallId: approval.toolCallId,
681
- toolName: approval.toolName,
682
- args: Object.assign(Object.assign({}, (typeof approval.args === 'object' && approval.args !== null
683
- ? approval.args
684
- : {})), (approval.reason
685
- ? { __approvalReason: approval.reason }
686
- : {})),
687
- };
688
- const idx = lastToolCalls.findIndex((tc) => tc.toolCallId === approval.toolCallId && !tc.result);
689
- if (idx !== -1) {
690
- lastToolCalls[idx] = approvalToolCall;
691
- }
692
- else {
693
- lastToolCalls.push(approvalToolCall);
694
- }
695
- }
696
- const updatedContent = buildRichContent(lastText, lastStructuredParts, lastToolCalls);
697
- yield yield __await({
698
- content: updatedContent,
699
- status: {
700
- type: 'requires-action',
701
- reason: 'tool-calls',
702
- },
703
- });
704
- }
705
- else if (content.length > 0) {
706
- yield yield __await({ content });
707
- }
708
- return yield __await(void 0);
709
- }
710
- // Normal flow: new user message → non-streaming POST
711
- const lastUserMsg = [...messages].reverse().find((m) => m.role === 'user');
712
- if (!lastUserMsg)
713
- return yield __await(void 0);
714
- let messageText = '';
715
- if (lastUserMsg.content) {
716
- for (const part of lastUserMsg.content) {
717
- if ('text' in part && part.type === 'text') {
718
- messageText += part.text;
719
- }
720
- }
721
- }
722
- const response = yield __await(fetch(`${opts.api}/${opts.agentName}`, {
723
- method: 'POST',
724
- headers: Object.assign({ 'Content-Type': 'application/json' }, opts.headers),
725
- body: JSON.stringify(Object.assign({ agentName: opts.agentName, message: messageText, threadId: opts.threadId, resourceId: opts.resourceId, model: opts.model, temperature: opts.temperature }, (opts.context ? { context: opts.context } : {}))),
726
- signal: abortSignal,
727
- credentials: opts.credentials,
728
- }));
729
- if (!response.ok) {
730
- throw new Error(`Agent run failed: ${response.status}`);
731
- }
732
- const json = yield __await(response.json());
733
- if (json.status === 'suspended' && ((_e = json.pendingApprovals) === null || _e === void 0 ? void 0 : _e.length) > 0) {
734
- const approvals = json.pendingApprovals;
735
- pendingApprovalsRef.current = approvals;
736
- setPendingApprovalsRef.current(approvals);
737
- const toolCalls = approvals.map((approval) => ({
738
- type: 'tool-call',
739
- toolCallId: approval.toolCallId,
740
- toolName: approval.toolName,
741
- args: Object.assign(Object.assign({}, (typeof approval.args === 'object' && approval.args !== null
742
- ? approval.args
743
- : {})), (approval.reason ? { __approvalReason: approval.reason } : {})),
744
- }));
745
- const content = [];
746
- content.push(...buildContentFromAgentResult(json.result));
747
- content.push(...toolCalls);
748
- yield yield __await({
749
- content,
750
- status: {
751
- type: 'requires-action',
752
- reason: 'tool-calls',
227
+ const onFinishRef = (0, react_1.useRef)(options.onFinish);
228
+ onFinishRef.current = options.onFinish;
229
+ const agent = (0, react_1.useMemo)(() => new PikkuAgent(options),
230
+ // agent is intentionally created once; opts are synced via updateOpts
231
+ // eslint-disable-next-line react-hooks/exhaustive-deps
232
+ []);
233
+ (0, react_1.useEffect)(() => {
234
+ agent.updateOpts(options);
235
+ });
236
+ (0, react_1.useEffect)(() => {
237
+ const { unsubscribe } = agent.subscribe({
238
+ onCustomEvent: ({ event }) => {
239
+ const e = event;
240
+ if (e.name === 'pikku:approval-request') {
241
+ const v = e.value;
242
+ commitPending([
243
+ ...pendingApprovalsRef.current,
244
+ {
245
+ toolCallId: v.toolCallId,
246
+ toolName: v.toolName,
247
+ args: v.args,
248
+ reason: v.reason,
249
+ runId: v.runId,
250
+ type: 'approval-request',
753
251
  },
754
- });
755
- return yield __await(void 0);
252
+ ]);
756
253
  }
757
- // No approvals yield complete content
758
- (_f = onFinishRef.current) === null || _f === void 0 ? void 0 : _f.call(onFinishRef);
759
- const content = buildContentFromAgentResult(json.result);
760
- if (content.length > 0) {
761
- yield yield __await({ content });
254
+ else if (e.name === 'pikku:credential-request') {
255
+ const v = e.value;
256
+ commitPending([
257
+ ...pendingApprovalsRef.current,
258
+ {
259
+ toolCallId: v.toolCallId,
260
+ toolName: v.toolName,
261
+ args: v.args,
262
+ runId: v.runId,
263
+ type: 'credential-request',
264
+ credentialName: v.credentialName,
265
+ credentialType: v.credentialType,
266
+ connectUrl: v.connectUrl,
267
+ },
268
+ ]);
762
269
  }
763
- });
764
- },
765
- };
766
- }
767
- function usePikkuAgentNonStreamingRuntime(options) {
768
- const [pendingApprovals, setPendingApprovals] = (0, react_1.useState)([]);
270
+ },
271
+ onRunFinalized: () => {
272
+ var _a;
273
+ (_a = onFinishRef.current) === null || _a === void 0 ? void 0 : _a.call(onFinishRef);
274
+ },
275
+ });
276
+ return unsubscribe;
277
+ }, [agent, commitPending]);
278
+ // Hydrate the thread from prior messages via the AG-UI history adapter,
279
+ // which useAgUiRuntime loads once on mount. Built once — see the
280
+ // initialMessages doc note about keeping the chat unmounted until ready.
281
+ const history = (0, react_1.useMemo)(() => {
282
+ var _a;
283
+ if (!((_a = options.initialMessages) === null || _a === void 0 ? void 0 : _a.length))
284
+ return undefined;
285
+ const repository = react_2.ExportedMessageRepository.fromArray(options.initialMessages);
286
+ return {
287
+ load: () => __awaiter(this, void 0, void 0, function* () { return repository; }),
288
+ append: () => __awaiter(this, void 0, void 0, function* () { }),
289
+ };
290
+ // eslint-disable-next-line react-hooks/exhaustive-deps
291
+ }, []);
292
+ const runtime = (0, react_ag_ui_1.useAgUiRuntime)(history ? { agent, adapters: { history } } : { agent });
769
293
  const optionsRef = (0, react_1.useRef)(options);
770
294
  optionsRef.current = options;
771
- const onFinishRef = (0, react_1.useRef)(options.onFinish);
772
- onFinishRef.current = options.onFinish;
773
- const pendingApprovalsRef = (0, react_1.useRef)([]);
774
- const approvalDecisionsRef = (0, react_1.useRef)([]);
775
- const setPendingApprovalsRef = (0, react_1.useRef)(setPendingApprovals);
776
- setPendingApprovalsRef.current = setPendingApprovals;
777
- const adapter = (0, react_1.useMemo)(() => createPikkuNonStreamingAdapter(optionsRef, pendingApprovalsRef, approvalDecisionsRef, setPendingApprovalsRef, onFinishRef), []);
778
- const initialMessages = (0, react_1.useMemo)(() => options.initialMessages
779
- ? (0, exports.convertDbMessages)(options.initialMessages)
780
- : undefined, [options.initialMessages]);
781
- const runtime = (0, react_2.useLocalRuntime)(adapter, { initialMessages });
782
- const handleApproval = (0, react_1.useCallback)((toolCallId, approved) => {
783
- approvalDecisionsRef.current.push({ toolCallId, approved });
784
- }, []);
785
- const isAwaitingApproval = pendingApprovals.length > 0;
295
+ const resumeChainRef = (0, react_1.useRef)(Promise.resolve());
296
+ // The runtime only aggregates events from runs it starts itself, so the
297
+ // final approval is queued on the agent and triggered by the caller's
298
+ // addResult (which makes the runtime start the resume run once every tool
299
+ // call has a result). Earlier approvals in a batch are acknowledged with
300
+ // plain requests — their stream carries no content beyond 'done'.
301
+ const handleApproval = (0, react_1.useCallback)((toolCallId, approved) => __awaiter(this, void 0, void 0, function* () {
302
+ const approval = pendingApprovalsRef.current.find((p) => p.toolCallId === toolCallId);
303
+ if (!approval || !approval.runId)
304
+ return false;
305
+ const remaining = pendingApprovalsRef.current.filter((p) => p.toolCallId !== toolCallId);
306
+ commitPending(remaining);
307
+ const resume = { runId: approval.runId, toolCallId, approved };
308
+ if (remaining.length > 0) {
309
+ resumeChainRef.current = resumeChainRef.current.then(() => __awaiter(this, void 0, void 0, function* () {
310
+ const opts = optionsRef.current;
311
+ try {
312
+ const response = yield fetch(`${opts.api}/${opts.agentName}/resume`, {
313
+ method: 'POST',
314
+ headers: Object.assign({ 'Content-Type': 'application/json', Accept: 'text/event-stream' }, opts.headers),
315
+ credentials: opts.credentials,
316
+ body: JSON.stringify(resume),
317
+ });
318
+ yield response.text();
319
+ }
320
+ catch (err) {
321
+ console.error('[pikku] failed to resolve approval', err);
322
+ }
323
+ }));
324
+ yield resumeChainRef.current;
325
+ return true;
326
+ }
327
+ yield resumeChainRef.current;
328
+ agent.queueResume(resume);
329
+ return true;
330
+ }), [agent, commitPending]);
786
331
  return {
787
332
  runtime,
788
333
  pendingApprovals,
789
- isAwaitingApproval,
334
+ isAwaitingApproval: pendingApprovals.length > 0,
790
335
  handleApproval,
791
336
  };
792
337
  }