@parallelworks/ui 0.75.0 → 0.76.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +13 -0
- package/dist/ai/adapter/openai/createOpenAIStream.js +1 -0
- package/dist/ai/adapter/openai/sse.d.ts +2 -1
- package/dist/ai/adapter/openai/sse.js +20 -0
- package/dist/ai/adapter/openai/wire.d.ts +5 -0
- package/dist/ai/adapter/types.d.ts +11 -0
- package/dist/ai/components/ChatMessage.js +30 -5
- package/dist/ai/components/ChatThread.js +4 -3
- package/dist/ai/components/ComposerAddMenu.js +17 -6
- package/dist/ai/components/ShareDialog.d.ts +3 -0
- package/dist/ai/components/ShareDialog.js +49 -13
- package/dist/ai/components/agent/AgentMessageParts.js +62 -23
- package/dist/ai/core/ChatProvider.d.ts +3 -1
- package/dist/ai/core/ChatProvider.js +215 -131
- package/dist/ai/core/parts.js +5 -1
- package/dist/ai/strings.d.ts +15 -0
- package/dist/ai/strings.js +14 -6
- package/dist/ai/types.d.ts +5 -1
- package/dist/components/relativeTime.js +1 -1
- package/dist/styles.css +1 -1
- package/package.json +1 -1
|
@@ -44,6 +44,20 @@ function heldSendArgs(held) {
|
|
|
44
44
|
preceding.length > 0 ? preceding : void 0
|
|
45
45
|
];
|
|
46
46
|
}
|
|
47
|
+
/** The wire history for the messages shown, oldest first. */
|
|
48
|
+
function historyMessages(messages, selectedProvider) {
|
|
49
|
+
return messages.filter((m) => m.content || m.toolCalls?.length || m.parts?.length).flatMap((m) => {
|
|
50
|
+
const msg = {
|
|
51
|
+
role: m.role,
|
|
52
|
+
content: m.content || null
|
|
53
|
+
};
|
|
54
|
+
if (m.role === "assistant" && m.reasoning) msg.reasoning_content = m.reasoning;
|
|
55
|
+
if (m.role === "assistant" && m.toolCalls?.length) msg.tool_calls = m.toolCalls;
|
|
56
|
+
if (m.role === "assistant" && m.responsesOutput?.length && m.model === selectedProvider) msg.responses_output = m.responsesOutput;
|
|
57
|
+
if (m.role === "tool" && m.toolCallId) msg.tool_call_id = m.toolCallId;
|
|
58
|
+
return m.role === "assistant" ? toolTurnMessages(m.parts, msg) : [msg];
|
|
59
|
+
});
|
|
60
|
+
}
|
|
47
61
|
var NO_TOOLS = [];
|
|
48
62
|
var ChatContext = createContext(void 0);
|
|
49
63
|
var ChatStreamContext = createContext(void 0);
|
|
@@ -291,6 +305,124 @@ function ChatProvider({ adapter, currentUser, navigation, activeConversationId =
|
|
|
291
305
|
state.currentConversation,
|
|
292
306
|
navigation
|
|
293
307
|
]);
|
|
308
|
+
const streamReply = useCallback(async (targetConversationId, request, parentId, onFailure, routePart) => {
|
|
309
|
+
backgroundStreamsRef.current.set(targetConversationId, {
|
|
310
|
+
content: "",
|
|
311
|
+
reasoning: "",
|
|
312
|
+
parts: [],
|
|
313
|
+
messageId: null,
|
|
314
|
+
isActive: true
|
|
315
|
+
});
|
|
316
|
+
dispatch({
|
|
317
|
+
type: "SET_STREAMING",
|
|
318
|
+
isStreaming: true,
|
|
319
|
+
conversationId: targetConversationId
|
|
320
|
+
});
|
|
321
|
+
dispatch({
|
|
322
|
+
type: "CLEAR_STREAMING_CONTENT",
|
|
323
|
+
conversationId: targetConversationId
|
|
324
|
+
});
|
|
325
|
+
const controller = new AbortController();
|
|
326
|
+
abortControllerRef.current = controller;
|
|
327
|
+
try {
|
|
328
|
+
let thinkingStart = null;
|
|
329
|
+
let thinkingEnd = null;
|
|
330
|
+
const markStreamStart = () => {
|
|
331
|
+
if (thinkingStart === null) thinkingStart = Date.now();
|
|
332
|
+
};
|
|
333
|
+
let liveParts = [];
|
|
334
|
+
const { content: assistantContent, messageId: assistantMessageId, toolCalls, model: resolvedModel, reasoning: assistantReasoning, responsesOutput } = await adapter.streamCompletion(request, {
|
|
335
|
+
onContent: (chunk) => {
|
|
336
|
+
markStreamStart();
|
|
337
|
+
onStreamingChunk(chunk);
|
|
338
|
+
},
|
|
339
|
+
onReasoning: (reasoning) => {
|
|
340
|
+
markStreamStart();
|
|
341
|
+
thinkingEnd = Date.now();
|
|
342
|
+
dispatch({
|
|
343
|
+
type: "APPEND_STREAMING_REASONING",
|
|
344
|
+
reasoning,
|
|
345
|
+
conversationId: targetConversationId
|
|
346
|
+
});
|
|
347
|
+
const bgStream = backgroundStreamsRef.current.get(targetConversationId);
|
|
348
|
+
if (bgStream) bgStream.reasoning += reasoning;
|
|
349
|
+
},
|
|
350
|
+
onMessageId: (messageId) => {
|
|
351
|
+
markStreamStart();
|
|
352
|
+
const bgStream = backgroundStreamsRef.current.get(targetConversationId);
|
|
353
|
+
if (bgStream) bgStream.messageId = messageId;
|
|
354
|
+
},
|
|
355
|
+
onPart: (delta) => {
|
|
356
|
+
markStreamStart();
|
|
357
|
+
if (routePart?.(delta)) return;
|
|
358
|
+
liveParts = applyPartDelta(liveParts, delta);
|
|
359
|
+
dispatch({
|
|
360
|
+
type: "APPLY_STREAMING_PART",
|
|
361
|
+
delta,
|
|
362
|
+
conversationId: targetConversationId
|
|
363
|
+
});
|
|
364
|
+
const bgStream = backgroundStreamsRef.current.get(targetConversationId);
|
|
365
|
+
if (bgStream) bgStream.parts = liveParts;
|
|
366
|
+
}
|
|
367
|
+
}, controller.signal);
|
|
368
|
+
const thinkingDuration = assistantReasoning && thinkingStart !== null ? (thinkingEnd ?? Date.now()) - thinkingStart : null;
|
|
369
|
+
const assistantMessage = {
|
|
370
|
+
id: assistantMessageId || crypto.randomUUID(),
|
|
371
|
+
parentId,
|
|
372
|
+
role: "assistant",
|
|
373
|
+
content: assistantContent || (toolCalls.length > 0 ? null : ""),
|
|
374
|
+
toolCalls: toolCalls.length > 0 ? toolCalls : void 0,
|
|
375
|
+
model: request.useResponses ? request.model : resolvedModel || request.model,
|
|
376
|
+
providerId: state.selectedProvider,
|
|
377
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
378
|
+
reasoning: assistantReasoning || void 0,
|
|
379
|
+
reasoningDuration: thinkingDuration ?? void 0,
|
|
380
|
+
responsesOutput: responsesOutput.length ? responsesOutput : void 0,
|
|
381
|
+
...liveParts.length ? { parts: finalizeParts(liveParts) } : {}
|
|
382
|
+
};
|
|
383
|
+
dispatch({
|
|
384
|
+
type: "ADD_MESSAGE",
|
|
385
|
+
message: assistantMessage,
|
|
386
|
+
conversationId: targetConversationId
|
|
387
|
+
});
|
|
388
|
+
return "sent";
|
|
389
|
+
} catch (error) {
|
|
390
|
+
if (error instanceof DOMException && error.name === "AbortError") return "aborted";
|
|
391
|
+
console.error("Failed to send message:", error);
|
|
392
|
+
onFailure(errorText(error));
|
|
393
|
+
return "failed";
|
|
394
|
+
} finally {
|
|
395
|
+
abortControllerRef.current = null;
|
|
396
|
+
const { unrevealed } = stopAnimationLoop();
|
|
397
|
+
if (unrevealed) dispatch({
|
|
398
|
+
type: "APPEND_STREAMING_CONTENT",
|
|
399
|
+
content: unrevealed,
|
|
400
|
+
conversationId: targetConversationId
|
|
401
|
+
});
|
|
402
|
+
streamingConversationIdRef.current = null;
|
|
403
|
+
backgroundStreamsRef.current.delete(targetConversationId);
|
|
404
|
+
setTimeout(() => {
|
|
405
|
+
streamingToConversationRef.current = null;
|
|
406
|
+
}, 500);
|
|
407
|
+
dispatch({
|
|
408
|
+
type: "SET_STREAMING",
|
|
409
|
+
isStreaming: false,
|
|
410
|
+
conversationId: targetConversationId
|
|
411
|
+
});
|
|
412
|
+
dispatch({
|
|
413
|
+
type: "CLEAR_STREAMING_CONTENT",
|
|
414
|
+
conversationId: targetConversationId
|
|
415
|
+
});
|
|
416
|
+
}
|
|
417
|
+
}, [
|
|
418
|
+
adapter,
|
|
419
|
+
state.selectedProvider,
|
|
420
|
+
onStreamingChunk,
|
|
421
|
+
stopAnimationLoop,
|
|
422
|
+
streamingConversationIdRef,
|
|
423
|
+
backgroundStreamsRef,
|
|
424
|
+
abortControllerRef
|
|
425
|
+
]);
|
|
294
426
|
const sendMessage = useCallback(async (content, attachmentIds, parentId, attachmentMeta, precedingMessages) => {
|
|
295
427
|
if (isSendingRef.current) {
|
|
296
428
|
const convId = currentConversationIdRef.current;
|
|
@@ -405,13 +537,6 @@ function ChatProvider({ adapter, currentUser, navigation, activeConversationId =
|
|
|
405
537
|
message: userMessage,
|
|
406
538
|
conversationId: targetConversationId
|
|
407
539
|
});
|
|
408
|
-
backgroundStreamsRef.current.set(targetConversationId, {
|
|
409
|
-
content: "",
|
|
410
|
-
reasoning: "",
|
|
411
|
-
parts: [],
|
|
412
|
-
messageId: null,
|
|
413
|
-
isActive: true
|
|
414
|
-
});
|
|
415
540
|
const setMessageError = (errorMsg) => {
|
|
416
541
|
dispatch({
|
|
417
542
|
type: "UPDATE_MESSAGE",
|
|
@@ -421,17 +546,7 @@ function ChatProvider({ adapter, currentUser, navigation, activeConversationId =
|
|
|
421
546
|
});
|
|
422
547
|
};
|
|
423
548
|
const apiMessages = [
|
|
424
|
-
...(isNewConversation ? [] : state.currentConversation?.messages || []
|
|
425
|
-
const msg = {
|
|
426
|
-
role: m.role,
|
|
427
|
-
content: m.content || null
|
|
428
|
-
};
|
|
429
|
-
if (m.role === "assistant" && m.reasoning) msg.reasoning_content = m.reasoning;
|
|
430
|
-
if (m.role === "assistant" && m.toolCalls?.length) msg.tool_calls = m.toolCalls;
|
|
431
|
-
if (m.role === "assistant" && m.responsesOutput?.length && m.model === state.selectedProvider) msg.responses_output = m.responsesOutput;
|
|
432
|
-
if (m.role === "tool" && m.toolCallId) msg.tool_call_id = m.toolCallId;
|
|
433
|
-
return m.role === "assistant" ? toolTurnMessages(m.parts, msg) : [msg];
|
|
434
|
-
}),
|
|
549
|
+
...historyMessages(isNewConversation ? [] : state.currentConversation?.messages || [], state.selectedProvider),
|
|
435
550
|
...precedingApiMessages,
|
|
436
551
|
{
|
|
437
552
|
role: "user",
|
|
@@ -439,28 +554,10 @@ function ChatProvider({ adapter, currentUser, navigation, activeConversationId =
|
|
|
439
554
|
attachment_ids: attachmentIds?.length ? attachmentIds : void 0
|
|
440
555
|
}
|
|
441
556
|
];
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
isStreaming: true,
|
|
445
|
-
conversationId: targetConversationId
|
|
446
|
-
});
|
|
447
|
-
dispatch({
|
|
448
|
-
type: "CLEAR_STREAMING_CONTENT",
|
|
449
|
-
conversationId: targetConversationId
|
|
450
|
-
});
|
|
451
|
-
const controller = new AbortController();
|
|
452
|
-
abortControllerRef.current = controller;
|
|
453
|
-
let sendSucceeded = false;
|
|
454
|
-
let wasAborted = false;
|
|
557
|
+
const useResponses = state.models.find((m) => m.id === modelString)?.supports_responses === true;
|
|
558
|
+
let outcome = "failed";
|
|
455
559
|
try {
|
|
456
|
-
|
|
457
|
-
let thinkingStart = null;
|
|
458
|
-
let thinkingEnd = null;
|
|
459
|
-
const markStreamStart = () => {
|
|
460
|
-
if (thinkingStart === null) thinkingStart = Date.now();
|
|
461
|
-
};
|
|
462
|
-
let liveParts = [];
|
|
463
|
-
const { content: assistantContent, messageId: assistantMessageId, toolCalls, model: resolvedModel, reasoning: assistantReasoning, responsesOutput } = await adapter.streamCompletion({
|
|
560
|
+
outcome = await streamReply(targetConversationId, {
|
|
464
561
|
model: modelString,
|
|
465
562
|
messages: apiMessages,
|
|
466
563
|
conversationId,
|
|
@@ -469,94 +566,11 @@ function ChatProvider({ adapter, currentUser, navigation, activeConversationId =
|
|
|
469
566
|
allocation: state.selectedAllocation,
|
|
470
567
|
useResponses,
|
|
471
568
|
...tools ? { tools: enabledToolIds } : {}
|
|
472
|
-
},
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
onStreamingChunk(chunk);
|
|
476
|
-
},
|
|
477
|
-
onReasoning: (reasoning) => {
|
|
478
|
-
markStreamStart();
|
|
479
|
-
thinkingEnd = Date.now();
|
|
480
|
-
dispatch({
|
|
481
|
-
type: "APPEND_STREAMING_REASONING",
|
|
482
|
-
reasoning,
|
|
483
|
-
conversationId: targetConversationId
|
|
484
|
-
});
|
|
485
|
-
const bgStream = backgroundStreamsRef.current.get(targetConversationId);
|
|
486
|
-
if (bgStream) bgStream.reasoning += reasoning;
|
|
487
|
-
},
|
|
488
|
-
onMessageId: (messageId) => {
|
|
489
|
-
markStreamStart();
|
|
490
|
-
const bgStream = backgroundStreamsRef.current.get(targetConversationId);
|
|
491
|
-
if (bgStream) bgStream.messageId = messageId;
|
|
492
|
-
},
|
|
493
|
-
onPart: (delta) => {
|
|
494
|
-
markStreamStart();
|
|
495
|
-
liveParts = applyPartDelta(liveParts, delta);
|
|
496
|
-
dispatch({
|
|
497
|
-
type: "APPLY_STREAMING_PART",
|
|
498
|
-
delta,
|
|
499
|
-
conversationId: targetConversationId
|
|
500
|
-
});
|
|
501
|
-
const bgStream = backgroundStreamsRef.current.get(targetConversationId);
|
|
502
|
-
if (bgStream) bgStream.parts = liveParts;
|
|
503
|
-
}
|
|
504
|
-
}, controller.signal);
|
|
505
|
-
const thinkingDuration = assistantReasoning && thinkingStart !== null ? (thinkingEnd ?? Date.now()) - thinkingStart : null;
|
|
506
|
-
const assistantMessage = {
|
|
507
|
-
id: assistantMessageId || crypto.randomUUID(),
|
|
508
|
-
parentId: userMessageId,
|
|
509
|
-
role: "assistant",
|
|
510
|
-
content: assistantContent || (toolCalls.length > 0 ? null : ""),
|
|
511
|
-
toolCalls: toolCalls.length > 0 ? toolCalls : void 0,
|
|
512
|
-
model: useResponses ? modelString : resolvedModel || modelString,
|
|
513
|
-
providerId: state.selectedProvider,
|
|
514
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
515
|
-
reasoning: assistantReasoning || void 0,
|
|
516
|
-
reasoningDuration: thinkingDuration ?? void 0,
|
|
517
|
-
responsesOutput: responsesOutput.length ? responsesOutput : void 0,
|
|
518
|
-
...liveParts.length ? { parts: finalizeParts(liveParts) } : {}
|
|
519
|
-
};
|
|
520
|
-
dispatch({
|
|
521
|
-
type: "ADD_MESSAGE",
|
|
522
|
-
message: assistantMessage,
|
|
523
|
-
conversationId: targetConversationId
|
|
524
|
-
});
|
|
525
|
-
if (isNewConversation) loadConversations();
|
|
526
|
-
sendSucceeded = true;
|
|
527
|
-
return true;
|
|
528
|
-
} catch (error) {
|
|
529
|
-
if (error instanceof DOMException && error.name === "AbortError") {
|
|
530
|
-
wasAborted = true;
|
|
531
|
-
return false;
|
|
532
|
-
}
|
|
533
|
-
console.error("Failed to send message:", error);
|
|
534
|
-
setMessageError(errorText(error));
|
|
535
|
-
return false;
|
|
569
|
+
}, userMessageId, setMessageError);
|
|
570
|
+
if (outcome === "sent" && isNewConversation) loadConversations();
|
|
571
|
+
return outcome === "sent";
|
|
536
572
|
} finally {
|
|
537
|
-
|
|
538
|
-
const { unrevealed } = stopAnimationLoop();
|
|
539
|
-
if (unrevealed) dispatch({
|
|
540
|
-
type: "APPEND_STREAMING_CONTENT",
|
|
541
|
-
content: unrevealed,
|
|
542
|
-
conversationId: targetConversationId
|
|
543
|
-
});
|
|
544
|
-
streamingConversationIdRef.current = null;
|
|
545
|
-
backgroundStreamsRef.current.delete(targetConversationId);
|
|
546
|
-
setTimeout(() => {
|
|
547
|
-
streamingToConversationRef.current = null;
|
|
548
|
-
}, 500);
|
|
549
|
-
dispatch({
|
|
550
|
-
type: "SET_STREAMING",
|
|
551
|
-
isStreaming: false,
|
|
552
|
-
conversationId: targetConversationId
|
|
553
|
-
});
|
|
554
|
-
dispatch({
|
|
555
|
-
type: "CLEAR_STREAMING_CONTENT",
|
|
556
|
-
conversationId: targetConversationId
|
|
557
|
-
});
|
|
558
|
-
if (wasAborted) isSendingRef.current = false;
|
|
559
|
-
else if (sendSucceeded) {
|
|
573
|
+
if (outcome === "sent") {
|
|
560
574
|
const next = heldSendArgs(pendingQueueRef.current);
|
|
561
575
|
pendingQueueRef.current = [];
|
|
562
576
|
dispatch({ type: "CLEAR_QUEUED_MESSAGES" });
|
|
@@ -581,16 +595,84 @@ function ChatProvider({ adapter, currentUser, navigation, activeConversationId =
|
|
|
581
595
|
createConversation,
|
|
582
596
|
loadConversations,
|
|
583
597
|
navigation,
|
|
584
|
-
onStreamingChunk,
|
|
585
598
|
startStreaming,
|
|
586
|
-
|
|
587
|
-
streamingConversationIdRef,
|
|
588
|
-
backgroundStreamsRef,
|
|
589
|
-
abortControllerRef
|
|
599
|
+
streamReply
|
|
590
600
|
]);
|
|
591
601
|
useEffect(() => {
|
|
592
602
|
sendMessageRef.current = sendMessage;
|
|
593
603
|
}, [sendMessage]);
|
|
604
|
+
const answerToolApproval = useCallback(async (approvalId, answer) => {
|
|
605
|
+
const conversation = state.currentConversation;
|
|
606
|
+
const modelString = state.selectedProvider;
|
|
607
|
+
if (answer.kind !== "confirm" || !conversation || !modelString || isSendingRef.current) return;
|
|
608
|
+
const pausedAt = conversation.messages.findIndex((m) => m.role === "assistant" && m.parts?.some((p) => p.kind === "approval" && p.id === approvalId && !p.resolved));
|
|
609
|
+
const paused = conversation.messages[pausedAt];
|
|
610
|
+
if (!paused) return;
|
|
611
|
+
isSendingRef.current = true;
|
|
612
|
+
const targetConversationId = conversation.id;
|
|
613
|
+
const t = resolvedConfig.strings.agentTranscript;
|
|
614
|
+
let pausedParts = paused.parts ?? [];
|
|
615
|
+
const updatePaused = (updates) => dispatch({
|
|
616
|
+
type: "UPDATE_MESSAGE",
|
|
617
|
+
messageId: paused.id,
|
|
618
|
+
updates,
|
|
619
|
+
conversationId: targetConversationId
|
|
620
|
+
});
|
|
621
|
+
const applyToPaused = (delta) => {
|
|
622
|
+
pausedParts = applyPartDelta(pausedParts, delta);
|
|
623
|
+
updatePaused({ parts: pausedParts });
|
|
624
|
+
};
|
|
625
|
+
applyToPaused({
|
|
626
|
+
type: "approval_resolved",
|
|
627
|
+
id: approvalId,
|
|
628
|
+
answer: answer.allowed ? t.approvalAllowed : t.approvalDenied
|
|
629
|
+
});
|
|
630
|
+
applyToPaused({
|
|
631
|
+
type: "tool_decision",
|
|
632
|
+
id: approvalId,
|
|
633
|
+
decision: {
|
|
634
|
+
status: answer.allowed ? "approved" : "denied",
|
|
635
|
+
by: "user",
|
|
636
|
+
name: currentUser.name || currentUser.username,
|
|
637
|
+
at: DateTime.now().toUTC().toISO()
|
|
638
|
+
}
|
|
639
|
+
});
|
|
640
|
+
streamingToConversationRef.current = targetConversationId;
|
|
641
|
+
startStreaming(targetConversationId);
|
|
642
|
+
try {
|
|
643
|
+
await streamReply(targetConversationId, {
|
|
644
|
+
model: modelString,
|
|
645
|
+
messages: historyMessages(conversation.messages.slice(0, pausedAt + 1), state.selectedProvider),
|
|
646
|
+
conversationId: targetConversationId,
|
|
647
|
+
parentMessageId: paused.id,
|
|
648
|
+
userMessageId: crypto.randomUUID(),
|
|
649
|
+
allocation: state.selectedAllocation,
|
|
650
|
+
...tools ? { tools: enabledToolIds } : {},
|
|
651
|
+
resume: {
|
|
652
|
+
messageId: paused.id,
|
|
653
|
+
toolCallId: approvalId,
|
|
654
|
+
allowed: answer.allowed,
|
|
655
|
+
...answer.denyMessage ? { feedback: answer.denyMessage } : {}
|
|
656
|
+
}
|
|
657
|
+
}, paused.id, (message) => updatePaused({ error: message }), (delta) => {
|
|
658
|
+
if (delta.type !== "tool_end" || delta.id !== approvalId) return false;
|
|
659
|
+
applyToPaused(delta);
|
|
660
|
+
return true;
|
|
661
|
+
});
|
|
662
|
+
} finally {
|
|
663
|
+
isSendingRef.current = false;
|
|
664
|
+
}
|
|
665
|
+
}, [
|
|
666
|
+
resolvedConfig,
|
|
667
|
+
state.currentConversation,
|
|
668
|
+
state.selectedProvider,
|
|
669
|
+
state.selectedAllocation,
|
|
670
|
+
tools,
|
|
671
|
+
enabledToolIds,
|
|
672
|
+
startStreaming,
|
|
673
|
+
streamReply,
|
|
674
|
+
currentUser
|
|
675
|
+
]);
|
|
594
676
|
const updateConversationTitle = useCallback(async (id, title) => {
|
|
595
677
|
try {
|
|
596
678
|
await adapter.conversations.rename(id, title);
|
|
@@ -807,6 +889,7 @@ function ChatProvider({ adapter, currentUser, navigation, activeConversationId =
|
|
|
807
889
|
deleteConversation,
|
|
808
890
|
sendMessage,
|
|
809
891
|
retryMessage,
|
|
892
|
+
answerToolApproval,
|
|
810
893
|
setSelectedProvider,
|
|
811
894
|
setSelectedAllocation,
|
|
812
895
|
setToolEnabled,
|
|
@@ -854,6 +937,7 @@ function ChatProvider({ adapter, currentUser, navigation, activeConversationId =
|
|
|
854
937
|
deleteConversation,
|
|
855
938
|
sendMessage,
|
|
856
939
|
retryMessage,
|
|
940
|
+
answerToolApproval,
|
|
857
941
|
setSelectedProvider,
|
|
858
942
|
setSelectedAllocation,
|
|
859
943
|
setToolEnabled,
|
package/dist/ai/core/parts.js
CHANGED
|
@@ -34,6 +34,10 @@ function applyPartDelta(parts, delta) {
|
|
|
34
34
|
...p,
|
|
35
35
|
args: p.args + delta.args
|
|
36
36
|
} : p);
|
|
37
|
+
case "tool_wait": return parts.map((p) => p.kind === "tool_call" && p.id === delta.id ? {
|
|
38
|
+
...p,
|
|
39
|
+
status: "waiting"
|
|
40
|
+
} : p);
|
|
37
41
|
case "tool_end": return parts.map((p) => p.kind === "tool_call" && p.id === delta.id ? {
|
|
38
42
|
...p,
|
|
39
43
|
status: delta.isError ? "error" : "ok",
|
|
@@ -99,7 +103,7 @@ function isStreamedText(part) {
|
|
|
99
103
|
* calls comes back as the answer alone. */
|
|
100
104
|
function toolTurnMessages(parts, answer) {
|
|
101
105
|
const calls = (parts ?? []).filter((p) => p.kind === "tool_call" && p.status !== "running" && p.result !== void 0);
|
|
102
|
-
if (calls.length === 0) return [answer];
|
|
106
|
+
if (calls.length === 0) return answer.content || answer.tool_calls?.length ? [answer] : [];
|
|
103
107
|
return [
|
|
104
108
|
{
|
|
105
109
|
role: "assistant",
|
package/dist/ai/strings.d.ts
CHANGED
|
@@ -15,6 +15,8 @@ export interface ChatStrings {
|
|
|
15
15
|
addGroups: string;
|
|
16
16
|
info: ReactNode;
|
|
17
17
|
shareButton: string;
|
|
18
|
+
/** Beside the share button in a shared conversation that can use tools. */
|
|
19
|
+
toolResultsShared: string;
|
|
18
20
|
};
|
|
19
21
|
blockedGroup: {
|
|
20
22
|
title: (group: string) => string;
|
|
@@ -67,6 +69,8 @@ export interface ChatStrings {
|
|
|
67
69
|
turnAllOff: string;
|
|
68
70
|
/** Closes the tools modal; each switch takes effect as it is flipped. */
|
|
69
71
|
done: string;
|
|
72
|
+
/** Marks a tool whose calls wait for the reader's approval. */
|
|
73
|
+
asksFirst: string;
|
|
70
74
|
};
|
|
71
75
|
allocationPicker: {
|
|
72
76
|
choose: string;
|
|
@@ -121,6 +125,9 @@ export interface ChatStrings {
|
|
|
121
125
|
activity: string;
|
|
122
126
|
/** A finished turn's label when it ran tools without reasoning. */
|
|
123
127
|
ranToolCalls: (count: number) => string;
|
|
128
|
+
/** The same, in a shared conversation, for a turn someone else started:
|
|
129
|
+
* its tools ran with that person's access. */
|
|
130
|
+
ranToolCallsAs: (count: number, name: string) => string;
|
|
124
131
|
};
|
|
125
132
|
thread: {
|
|
126
133
|
viewOnly: string;
|
|
@@ -243,6 +250,7 @@ export interface ChatStrings {
|
|
|
243
250
|
deniedByReviewer: string;
|
|
244
251
|
approvalExpired: string;
|
|
245
252
|
approvalTimedOut: string;
|
|
253
|
+
reviewTimedOut: string;
|
|
246
254
|
reviewFailed: string;
|
|
247
255
|
writeOwnAnswer: string;
|
|
248
256
|
subagent: string;
|
|
@@ -252,6 +260,13 @@ export interface ChatStrings {
|
|
|
252
260
|
approvalOptions: string;
|
|
253
261
|
/** Names a question the agent waits on that has no heading of its own. */
|
|
254
262
|
approvalLabel: string;
|
|
263
|
+
/** A call the reader approved or declined. */
|
|
264
|
+
approvalAllowed: string;
|
|
265
|
+
approvalDenied: string;
|
|
266
|
+
/** A call nobody answered in time. */
|
|
267
|
+
approvalRequestExpired: string;
|
|
268
|
+
/** A call only the person who asked for it may answer. */
|
|
269
|
+
approvalWaitingOn: (name: string) => string;
|
|
255
270
|
/** Names the field for the reason a request is denied. */
|
|
256
271
|
feedbackLabel: string;
|
|
257
272
|
/** Names the field for an answer of the reader's own. */
|
package/dist/ai/strings.js
CHANGED
|
@@ -20,7 +20,8 @@ var defaultChatStrings = {
|
|
|
20
20
|
/* @__PURE__ */ jsx("strong", { children: "Collaborate" }),
|
|
21
21
|
" permission allows adding messages."
|
|
22
22
|
] }),
|
|
23
|
-
shareButton: "Share conversation"
|
|
23
|
+
shareButton: "Share conversation",
|
|
24
|
+
toolResultsShared: "Tool results here are visible to the people it's shared with"
|
|
24
25
|
},
|
|
25
26
|
blockedGroup: {
|
|
26
27
|
title: (group) => `Billing group \u201c${group}\u201d is over its allocation`,
|
|
@@ -59,12 +60,13 @@ var defaultChatStrings = {
|
|
|
59
60
|
tools: "Tools",
|
|
60
61
|
someToolsOff: "Some tools are off",
|
|
61
62
|
toolsOn: (enabled, total) => `${enabled.toLocaleString()}/${total.toLocaleString()}`,
|
|
62
|
-
toolsDescription: "What the assistant may look up for you.",
|
|
63
|
+
toolsDescription: "What the assistant may look up or do for you.",
|
|
63
64
|
searchTools: "Search tools",
|
|
64
65
|
noToolsMatch: "No tools match",
|
|
65
66
|
turnAllOn: "Turn all on",
|
|
66
67
|
turnAllOff: "Turn all off",
|
|
67
|
-
done: "Done"
|
|
68
|
+
done: "Done",
|
|
69
|
+
asksFirst: "Asks first"
|
|
68
70
|
},
|
|
69
71
|
allocationPicker: {
|
|
70
72
|
choose: "Allocation",
|
|
@@ -150,7 +152,8 @@ var defaultChatStrings = {
|
|
|
150
152
|
thoughtFor: (duration) => `Thought for ${duration}`,
|
|
151
153
|
thoughtProcess: "Thought process",
|
|
152
154
|
activity: "Activity",
|
|
153
|
-
ranToolCalls: (count) => `Ran ${countOf(count, "tool call", "tool calls")}
|
|
155
|
+
ranToolCalls: (count) => `Ran ${countOf(count, "tool call", "tool calls")}`,
|
|
156
|
+
ranToolCallsAs: (count, name) => `Ran ${countOf(count, "tool call", "tool calls")} as ${name}`
|
|
154
157
|
},
|
|
155
158
|
thread: {
|
|
156
159
|
viewOnly: "You have view only access",
|
|
@@ -260,8 +263,9 @@ var defaultChatStrings = {
|
|
|
260
263
|
allowedByReviewer: "Allowed by the auto-reviewer",
|
|
261
264
|
deniedByReviewer: "Denied by the auto-reviewer",
|
|
262
265
|
approvalExpired: "Expired without an answer",
|
|
263
|
-
approvalTimedOut: "
|
|
264
|
-
|
|
266
|
+
approvalTimedOut: "Denied: no answer in time",
|
|
267
|
+
reviewTimedOut: "Denied: the auto-reviewer timed out",
|
|
268
|
+
reviewFailed: "Denied: the auto-reviewer failed",
|
|
265
269
|
writeOwnAnswer: "Write my own answer…",
|
|
266
270
|
subagent: "Task",
|
|
267
271
|
tasks: "Tasks",
|
|
@@ -269,6 +273,10 @@ var defaultChatStrings = {
|
|
|
269
273
|
approvalPending: "Waiting for an answer in the terminal",
|
|
270
274
|
approvalOptions: "Offered choices",
|
|
271
275
|
approvalLabel: "Waiting for your answer",
|
|
276
|
+
approvalAllowed: "Allowed",
|
|
277
|
+
approvalDenied: "Declined",
|
|
278
|
+
approvalRequestExpired: "This request expired. Ask again to run it.",
|
|
279
|
+
approvalWaitingOn: (name) => `Waiting for ${name} to answer`,
|
|
272
280
|
feedbackLabel: "Feedback for the agent",
|
|
273
281
|
ownAnswerLabel: "Your answer",
|
|
274
282
|
todoPending: "To do",
|
package/dist/ai/types.d.ts
CHANGED
|
@@ -45,7 +45,7 @@ export interface TokenUsage {
|
|
|
45
45
|
completion_tokens?: number;
|
|
46
46
|
total_tokens?: number;
|
|
47
47
|
}
|
|
48
|
-
export type ToolCallStatus = 'running' | 'ok' | 'error';
|
|
48
|
+
export type ToolCallStatus = 'running' | 'waiting' | 'ok' | 'error';
|
|
49
49
|
export interface TextPart {
|
|
50
50
|
kind: 'text';
|
|
51
51
|
text: string;
|
|
@@ -65,6 +65,8 @@ export interface ToolDecision {
|
|
|
65
65
|
/** ISO time of the answer. */
|
|
66
66
|
at?: string;
|
|
67
67
|
note?: string;
|
|
68
|
+
/** Why it was settled this way, such as the auto-reviewer's rationale. */
|
|
69
|
+
reason?: string;
|
|
68
70
|
}
|
|
69
71
|
export interface ToolCallPart {
|
|
70
72
|
kind: 'tool_call';
|
|
@@ -120,6 +122,8 @@ export interface ApprovalPart {
|
|
|
120
122
|
options?: ApprovalOption[];
|
|
121
123
|
plan?: string;
|
|
122
124
|
canAllowDirectory?: boolean;
|
|
125
|
+
expiresAt?: string;
|
|
126
|
+
waitingOn?: string;
|
|
123
127
|
resolved?: boolean;
|
|
124
128
|
answer?: string;
|
|
125
129
|
}
|
|
@@ -65,7 +65,7 @@ function useRelativeTime(value, format = "long") {
|
|
|
65
65
|
const unit = unitOf(age);
|
|
66
66
|
const key = `${format}:${at}:${unit}:${Math.trunc(age / unit)}`;
|
|
67
67
|
if (last.current?.key !== key) {
|
|
68
|
-
const label = format === "compact" ? compactLabel(age, t) : DateTime.fromMillis(at).toRelative({ base: DateTime.fromMillis(current) }) ?? "";
|
|
68
|
+
const label = format === "compact" ? compactLabel(age, t) : Math.abs(age) < SECOND ? t.now : DateTime.fromMillis(at).toRelative({ base: DateTime.fromMillis(current) }) ?? "";
|
|
69
69
|
last.current = {
|
|
70
70
|
key,
|
|
71
71
|
label
|