@jini-ai/chat 0.5.4 → 0.5.6
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/CHANGELOG.md +8 -0
- package/dist/core/ag-ui/entry.d.ts +33 -0
- package/dist/core/ag-ui/entry.js +80 -0
- package/dist/core/agentic/chat-capabilities.d.ts +2 -4
- package/dist/core/agentic/index.d.ts +4 -17
- package/dist/core/agentic/index.js +4 -17
- package/dist/core/assistant-client/a2ui-action-poster.d.ts +46 -0
- package/dist/core/assistant-client/a2ui-action-poster.js +113 -0
- package/dist/core/assistant-client/ag-ui-transport.d.ts +68 -0
- package/dist/core/assistant-client/ag-ui-transport.js +147 -0
- package/dist/core/assistant-client/attachment-liveness.d.ts +37 -0
- package/dist/core/assistant-client/attachment-liveness.js +93 -0
- package/dist/core/assistant-client/durable-run-subscription.d.ts +39 -0
- package/dist/core/assistant-client/durable-run-subscription.js +96 -0
- package/dist/core/assistant-client/entry.d.ts +13 -0
- package/dist/core/assistant-client/entry.js +7 -0
- package/dist/core/assistant-client/messages.d.ts +73 -0
- package/dist/core/assistant-client/messages.js +68 -0
- package/dist/core/assistant-client/transport.d.ts +164 -0
- package/dist/core/assistant-client/transport.js +734 -0
- package/dist/core/browser/page-actions.d.ts +121 -0
- package/dist/core/browser/page-actions.js +113 -0
- package/dist/core/durable-runs/continuation.d.ts +18 -0
- package/dist/core/durable-runs/continuation.js +61 -0
- package/dist/core/durable-runs/ledger-contracts.d.ts +53 -0
- package/dist/core/durable-runs/ledger-contracts.js +2 -0
- package/dist/core/durable-runs/policies.d.ts +6 -0
- package/dist/core/durable-runs/policies.js +6 -0
- package/dist/core/durable-runs/ports.d.ts +123 -0
- package/dist/core/durable-runs/ports.js +2 -0
- package/dist/core/durable-runs/run-contracts.d.ts +17 -0
- package/dist/core/durable-runs/run-contracts.js +2 -0
- package/dist/core/durable-runs/run-lock.d.ts +4 -0
- package/dist/core/durable-runs/run-lock.js +4 -0
- package/dist/core/index.d.ts +6 -19
- package/dist/core/index.js +6 -19
- package/dist/core/messages.d.ts +2 -0
- package/dist/core/question-form/format.d.ts +0 -9
- package/dist/core/question-form/format.js +16 -2
- package/dist/core/question-form/normalize.js +1 -0
- package/dist/core/question-form/secret-answers.d.ts +38 -0
- package/dist/core/question-form/secret-answers.js +45 -0
- package/dist/core/question-form/types.d.ts +2 -0
- package/dist/core/question-form.d.ts +1 -0
- package/dist/core/question-form.js +1 -0
- package/dist/core/run-events/entry.d.ts +208 -16
- package/dist/core/run-events/entry.js +273 -39
- package/dist/core/transcript.d.ts +3 -2
- package/dist/core/transcript.js +19 -3
- package/dist/core/user-text-redaction.d.ts +56 -0
- package/dist/core/user-text-redaction.js +89 -0
- package/dist/embed-widget/browser/page-actions.d.ts +1 -120
- package/dist/embed-widget/browser/page-actions.js +2 -112
- package/dist/embed-widget/react/embed.d.ts +1 -1
- package/dist/embed-widget/react/embed.js +1 -1
- package/dist/react/artifact-types.d.ts +9 -15
- package/dist/react/components/QuestionForm.d.ts +2 -1
- package/dist/react/components/QuestionForm.js +18 -12
- package/dist/react/components/QuestionsPanel.d.ts +3 -2
- package/dist/react/components/QuestionsPanel.js +2 -2
- package/dist/react/features/chat-pane/agent-tools.d.ts +1 -1
- package/dist/react/features/chat-pane/agent-tools.js +0 -1
- package/dist/react/features/chat-pane/components/AgentRuntimePicker.js +5 -2
- package/dist/react/features/chat-pane/components/ChatPane.d.ts +1 -1
- package/dist/react/features/chat-pane/components/ChatPane.js +5 -2
- package/dist/react/features/chat-pane/create-typed-answer-poster.d.ts +2 -1
- package/dist/react/features/chat-pane/create-typed-answer-poster.js +33 -1
- package/dist/react/features/chat-pane/hooks/useAgentRuntimePicker.hooks.js +3 -2
- package/dist/react/features/chat-pane/hooks/useChatPane.hooks.d.ts +4 -1
- package/dist/react/features/chat-pane/hooks/useChatPane.hooks.js +43 -22
- package/dist/react/features/chat-pane/model-options.d.ts +12 -0
- package/dist/react/features/chat-pane/model-options.js +55 -0
- package/dist/react/features/chat-pane/rules.d.ts +3 -2
- package/dist/react/features/chat-pane/rules.js +12 -4
- package/dist/react/features/chat-pane/types.d.ts +25 -1
- package/dist/react/hooks/assistant-chats-contracts.d.ts +164 -0
- package/dist/react/hooks/assistant-chats-contracts.js +2 -0
- package/dist/react/hooks/assistant-chats.d.ts +7 -0
- package/dist/react/hooks/assistant-chats.js +960 -0
- package/dist/react/hooks/attachment-preview-cache.d.ts +1 -1
- package/dist/react/hooks/attachment-preview-cache.js +1 -1
- package/dist/react/hooks/composer-draft-cache.d.ts +2 -2
- package/dist/react/hooks/composer-draft-cache.js +2 -2
- package/dist/react/hooks/fab-position.d.ts +90 -0
- package/dist/react/hooks/fab-position.js +271 -0
- package/dist/react/hooks/useConversation.d.ts +4 -1
- package/dist/react/hooks/useConversation.js +14 -12
- package/dist/react/hooks/useUserTextGuard.d.ts +11 -0
- package/dist/react/hooks/useUserTextGuard.js +19 -0
- package/dist/react/index.d.ts +12 -21
- package/dist/react/index.js +7 -0
- package/dist/server/durable-runs/accept.d.ts +25 -0
- package/dist/server/durable-runs/accept.js +21 -0
- package/dist/server/durable-runs/entry.d.ts +5 -0
- package/dist/server/durable-runs/entry.js +4 -0
- package/dist/server/durable-runs/recover.d.ts +5 -0
- package/dist/server/durable-runs/recover.js +147 -0
- package/dist/server/run-finalizer/ports.d.ts +2 -16
- package/dist/store/legacy/sqlite/messages.d.ts +24 -18
- package/dist/store/legacy/sqlite/messages.js +12 -2
- package/dist/store/pglite/index.d.ts +2 -1
- package/dist/store/postgres/index.d.ts +2 -1
- package/dist/store/runtime/chat-expiry-sweep.d.ts +40 -0
- package/dist/store/runtime/chat-expiry-sweep.js +70 -0
- package/dist/store/runtime/chat-history-store.d.ts +20 -0
- package/dist/store/runtime/chat-history-store.js +50 -0
- package/dist/store/runtime/durable-run-store.d.ts +21 -0
- package/dist/store/runtime/durable-run-store.js +342 -0
- package/dist/store/runtime/entry.d.ts +7 -0
- package/dist/store/runtime/entry.js +6 -0
- package/dist/store/runtime/kernel.d.ts +29 -0
- package/dist/store/runtime/kernel.js +2 -0
- package/dist/store/runtime/run-guarded-chat-store.d.ts +10 -0
- package/dist/store/runtime/run-guarded-chat-store.js +42 -0
- package/dist/store/runtime/run-ledger.d.ts +15 -0
- package/dist/store/runtime/run-ledger.js +206 -0
- package/dist/store/sql/boundary.d.ts +6 -0
- package/dist/store/sql/boundary.js +7 -1
- package/dist/store/sql/store.d.ts +3 -2
- package/dist/store/sql/store.js +9 -2
- package/dist/store/sqlite/index.d.ts +3 -2
- package/package.json +75 -10
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { pickerModelLabel } from '../model-options.js';
|
|
1
2
|
/**
|
|
2
3
|
* All of `AgentRuntimePicker`'s behavior, so the component itself is only markup.
|
|
3
4
|
*
|
|
@@ -146,12 +147,12 @@ export function useAgentRuntimePicker({ agents, value, placement, t, executionMo
|
|
|
146
147
|
const [position, setPosition] = useState();
|
|
147
148
|
const triggerRef = useRef(null);
|
|
148
149
|
const popoverRef = useRef(null);
|
|
149
|
-
const orderedAgents = useMemo(() => orderChatPaneAgents({ agents: agents.filter((agent) => agent.available !== false) }), [agents]);
|
|
150
|
+
const orderedAgents = useMemo(() => orderChatPaneAgents({ agents: agents.filter((agent) => agent.available !== false && agent.id !== 'gemini') }), [agents]);
|
|
150
151
|
const selectedAgent = orderedAgents.find((agent) => agent.id === value.agentId);
|
|
151
152
|
// `selectedAgent` stays resolved in BOTH modes on purpose — the CLI selection is still the stored
|
|
152
153
|
// one, and switching back to Local CLI must not have silently lost it. What changes in `'api'` is
|
|
153
154
|
// only what gets DESCRIBED, because none of the CLI's labels are true of an API turn.
|
|
154
|
-
const localModelLabel =
|
|
155
|
+
const localModelLabel = pickerModelLabel(selectedAgent, value.model, t);
|
|
155
156
|
const byok = byokSummaryText({ byokRuntime: byokRuntime, t: t });
|
|
156
157
|
const isApi = executionMode === 'api';
|
|
157
158
|
const modelLabel = isApi ? byok.modelLabel : localModelLabel;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { UserTextRedactionOptions } from '../../../../core/user-text-redaction.js';
|
|
1
2
|
import { type ChatPaneSendBlocker } from '../rules.js';
|
|
2
3
|
import type { ChatPaneActivity, ChatPaneAgent, ChatPaneAttachmentUploadOptions, ChatPaneAgentSelection, ChatPaneRunContext, ChatPaneWorkingDirectoryAccess, DeliverTypedAnswer, TypedAnswerNotice } from '../types.js';
|
|
3
4
|
import type { ChatAttachment, ChatMessage } from '@jini-ai/chat';
|
|
@@ -6,7 +7,7 @@ import type { ChatTransport } from '@jini-ai/chat';
|
|
|
6
7
|
import { type UseComposerResult } from '../../../hooks/useComposer.js';
|
|
7
8
|
import { type UseConversationResult } from '../../../hooks/useConversation.js';
|
|
8
9
|
import { type UseChatPaneWorkingDirectoryResult } from './useChatPaneWorkingDirectory.hooks.js';
|
|
9
|
-
export interface UseChatPaneOptions {
|
|
10
|
+
export interface UseChatPaneOptions extends UserTextRedactionOptions {
|
|
10
11
|
transport: ChatTransport;
|
|
11
12
|
agents: readonly ChatPaneAgent[];
|
|
12
13
|
initialMessages?: ChatMessage[];
|
|
@@ -37,6 +38,8 @@ export interface UseChatPaneOptions {
|
|
|
37
38
|
deliverTypedAnswer?: DeliverTypedAnswer;
|
|
38
39
|
}
|
|
39
40
|
export interface UseChatPaneResult extends UseChatPaneWorkingDirectoryResult {
|
|
41
|
+
/** Show a localized non-secret notice when a paste was removed. */
|
|
42
|
+
secretRedacted: boolean;
|
|
40
43
|
conversation: UseConversationResult;
|
|
41
44
|
composer: UseComposerResult;
|
|
42
45
|
selection: ChatPaneAgentSelection;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { useUserTextGuard } from '../../../hooks/useUserTextGuard.js';
|
|
1
2
|
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
2
3
|
import { describeChatPaneSendBlocker, findAwaitedTypedAnswerId, findChatPaneSendBlocker, isChatPaneQueueableBlocker, isTypedAnswerTurn, resolveChatPaneSelection, } from '../rules.js';
|
|
3
4
|
import { definedProps } from '../../../util/defined-props.js';
|
|
@@ -73,9 +74,11 @@ async function uploadAttachmentBatch(files, effects) {
|
|
|
73
74
|
}
|
|
74
75
|
}
|
|
75
76
|
export function useChatPane(options) {
|
|
77
|
+
const guard = useUserTextGuard(options);
|
|
76
78
|
const [activeUploadCount, setActiveUploadCount] = useState(0);
|
|
77
79
|
const [attachmentError, setAttachmentError] = useState(null);
|
|
78
|
-
|
|
80
|
+
// Keep the signal with sanitized queue text; re-redacting it later cannot detect the original paste.
|
|
81
|
+
const [queuedTurn, setQueuedTurn] = useState(null);
|
|
79
82
|
const [typedAnswerNotice, setTypedAnswerNotice] = useState(null);
|
|
80
83
|
// Set across the await of one typed-answer delivery so a second Enter cannot post the same text
|
|
81
84
|
// twice — the second post would find the question already answered and wrongly report it closed.
|
|
@@ -112,10 +115,19 @@ export function useChatPane(options) {
|
|
|
112
115
|
workingDirectoryAccess: options.workingDirectoryAccess,
|
|
113
116
|
} }));
|
|
114
117
|
const requestedSelection = options.selection ?? internalSelection;
|
|
115
|
-
const
|
|
118
|
+
const resolvedSelection = useMemo(() => resolveChatPaneSelection({ agents: options.agents, requested: requestedSelection }), [options.agents, requestedSelection]);
|
|
119
|
+
// Inline inventories and controlled selections can be new objects on every render. Keep the
|
|
120
|
+
// normalized value stable so synchronizing it into composer state cannot feed a render loop.
|
|
121
|
+
const selection = useMemo(() => definedProps({ source: {
|
|
122
|
+
agentId: resolvedSelection.agentId,
|
|
123
|
+
model: resolvedSelection.model,
|
|
124
|
+
reasoning: resolvedSelection.reasoning,
|
|
125
|
+
} }), [resolvedSelection.agentId, resolvedSelection.model, resolvedSelection.reasoning]);
|
|
116
126
|
const selectedAgent = options.agents.find((agent) => agent.id === selection.agentId);
|
|
117
127
|
const conversation = useConversation(definedProps({ source: {
|
|
118
128
|
transport: options.transport,
|
|
129
|
+
redactUserText: options.redactUserText,
|
|
130
|
+
onSecretRedacted: guard.notifyRedaction,
|
|
119
131
|
initialMessages: options.initialMessages,
|
|
120
132
|
conversationId: options.conversationId,
|
|
121
133
|
// Keys off an empty string, not `undefined` — `selection.agentId` is always a string (never
|
|
@@ -148,6 +160,8 @@ export function useChatPane(options) {
|
|
|
148
160
|
activeUploadsRef.current.clear();
|
|
149
161
|
};
|
|
150
162
|
}, []);
|
|
163
|
+
const selectionControlled = options.selection !== undefined;
|
|
164
|
+
const setComposerAgent = composer.setAgent;
|
|
151
165
|
useEffect(() => {
|
|
152
166
|
if (!selection.agentId)
|
|
153
167
|
return;
|
|
@@ -155,14 +169,14 @@ export function useChatPane(options) {
|
|
|
155
169
|
&& requestedSelection.model === selection.model
|
|
156
170
|
&& requestedSelection.reasoning === selection.reasoning)
|
|
157
171
|
return;
|
|
158
|
-
if (
|
|
172
|
+
if (!selectionControlled)
|
|
159
173
|
setInternalSelection(selection);
|
|
160
|
-
|
|
174
|
+
setComposerAgent(selection);
|
|
161
175
|
options.onSelectionChange?.(selection);
|
|
162
176
|
}, [
|
|
163
|
-
|
|
177
|
+
setComposerAgent,
|
|
164
178
|
options.onSelectionChange,
|
|
165
|
-
|
|
179
|
+
selectionControlled,
|
|
166
180
|
requestedSelection.agentId,
|
|
167
181
|
requestedSelection.model,
|
|
168
182
|
requestedSelection.reasoning,
|
|
@@ -175,6 +189,7 @@ export function useChatPane(options) {
|
|
|
175
189
|
options.onSelectionChange?.(validated);
|
|
176
190
|
}, [composer, options.agents, options.onSelectionChange]);
|
|
177
191
|
const sendBlocker = findChatPaneSendBlocker({
|
|
192
|
+
...(selection.model ? { model: selection.model } : {}),
|
|
178
193
|
selectedAgent,
|
|
179
194
|
isStreaming: conversation.isStreaming,
|
|
180
195
|
activeUploadCount,
|
|
@@ -267,8 +282,8 @@ export function useChatPane(options) {
|
|
|
267
282
|
}
|
|
268
283
|
}
|
|
269
284
|
}, [composer, options.conversationId, options.uploadAttachments]);
|
|
270
|
-
const
|
|
271
|
-
const trimmed =
|
|
285
|
+
const sendGuardedPrompt = useCallback(async ({ redaction }, _optional = {}) => {
|
|
286
|
+
const trimmed = redaction.text.trim();
|
|
272
287
|
if (!trimmed)
|
|
273
288
|
throw new Error('cannot send: the prompt is empty');
|
|
274
289
|
if (sendBlocker !== null) {
|
|
@@ -287,6 +302,7 @@ export function useChatPane(options) {
|
|
|
287
302
|
attachmentBatchIdRef.current = createAttachmentBatchId();
|
|
288
303
|
options.onActivityChange?.('queued');
|
|
289
304
|
await conversation.sendMessage(trimmed, definedProps({ source: {
|
|
305
|
+
secretRedaction: redaction.secretRedacted ? { secretRedacted: true, count: redaction.count } : undefined,
|
|
290
306
|
agentId: selection.agentId,
|
|
291
307
|
// Omitted when the array is EMPTY, not merely absent — an empty `attachments: []` would be a
|
|
292
308
|
// different (valid, present) value to the transport than "no attachments key at all".
|
|
@@ -302,12 +318,16 @@ export function useChatPane(options) {
|
|
|
302
318
|
sendBlocker,
|
|
303
319
|
workingDirectoryState.workingDirectory,
|
|
304
320
|
]);
|
|
321
|
+
const sendPrompt = useCallback(async (prompt) => {
|
|
322
|
+
await sendGuardedPrompt({ redaction: guard.redact({ text: prompt.trim() }, {}) }, {});
|
|
323
|
+
}, [guard.redact, sendGuardedPrompt]);
|
|
305
324
|
const answerWaitingQuestion = useCallback(async ({ deliver, text }) => {
|
|
325
|
+
const safeText = guard.redactText(text);
|
|
306
326
|
const generation = typedAnswerGenerationRef.current;
|
|
307
327
|
const submittedDraft = latestDraftRef.current;
|
|
308
328
|
const questionId = awaitedQuestionId;
|
|
309
329
|
deliveringTypedAnswerRef.current = true;
|
|
310
|
-
const outcome = await deliverTypedAnswerSafely({ deliver, text });
|
|
330
|
+
const outcome = await deliverTypedAnswerSafely({ deliver, text: safeText });
|
|
311
331
|
deliveringTypedAnswerRef.current = false;
|
|
312
332
|
if (!mountedRef.current || generation !== typedAnswerGenerationRef.current)
|
|
313
333
|
return;
|
|
@@ -323,21 +343,21 @@ export function useChatPane(options) {
|
|
|
323
343
|
// correction or next message typed while the host answered.
|
|
324
344
|
if (latestDraftRef.current === submittedDraft)
|
|
325
345
|
composer.setDraft('');
|
|
326
|
-
}, [awaitedQuestionId, composer]);
|
|
346
|
+
}, [awaitedQuestionId, composer, guard.redactText]);
|
|
327
347
|
// The ordinary next-turn path: queue behind a running run, else send now.
|
|
328
348
|
const sendOrdinaryTurn = useCallback(async (prompt) => {
|
|
329
349
|
// Queue instead of no-op'ing. `setDraft('')` and NOT `composer.reset()`: reset also discards
|
|
330
350
|
// staged attachments, which this turn still needs when it finally goes out.
|
|
331
351
|
if (isChatPaneQueueableBlocker({ blocker: sendBlocker })) {
|
|
332
352
|
queuedConversationIdRef.current = options.conversationId;
|
|
333
|
-
|
|
353
|
+
setQueuedTurn(guard.redact({ text: prompt }, {}));
|
|
334
354
|
composer.setDraft('');
|
|
335
355
|
return;
|
|
336
356
|
}
|
|
337
357
|
if (!canSend)
|
|
338
358
|
return;
|
|
339
359
|
await sendPrompt(prompt);
|
|
340
|
-
}, [canSend, composer, options.conversationId, sendBlocker, sendPrompt]);
|
|
360
|
+
}, [canSend, composer, options.conversationId, sendBlocker, sendPrompt, guard.redact]);
|
|
341
361
|
const send = useCallback(async () => {
|
|
342
362
|
const prompt = composerPrompt(composer);
|
|
343
363
|
if (!prompt || deliveringTypedAnswerRef.current)
|
|
@@ -385,31 +405,31 @@ export function useChatPane(options) {
|
|
|
385
405
|
if (!prompt)
|
|
386
406
|
return;
|
|
387
407
|
queuedConversationIdRef.current = options.conversationId;
|
|
388
|
-
|
|
408
|
+
setQueuedTurn(guard.redact({ text: prompt }, {}));
|
|
389
409
|
composer.setDraft('');
|
|
390
410
|
conversation.cancel();
|
|
391
|
-
}, [composer, conversation, options.conversationId]);
|
|
411
|
+
}, [composer, conversation, options.conversationId, guard.redact]);
|
|
392
412
|
const cancelQueued = useCallback(() => {
|
|
393
413
|
queuedConversationIdRef.current = undefined;
|
|
394
|
-
|
|
414
|
+
setQueuedTurn(null);
|
|
395
415
|
}, []);
|
|
396
416
|
// Flush on a FULLY clear blocker, not merely on streaming ending — see
|
|
397
417
|
// `isChatPaneQueueableBlocker`'s note about `findChatPaneSendBlocker`'s ordering. Clearing the
|
|
398
418
|
// queue slot BEFORE awaiting keeps a re-render from double-sending the same prompt.
|
|
399
419
|
useEffect(() => {
|
|
400
|
-
if (
|
|
420
|
+
if (queuedTurn === null || sendBlocker !== null)
|
|
401
421
|
return;
|
|
402
422
|
if (queuedConversationIdRef.current !== options.conversationId) {
|
|
403
423
|
// The conversation changed while this prompt waited behind a streaming run — sending it now
|
|
404
424
|
// would post it into a conversation the user never saw it queued against. Drop it instead
|
|
405
425
|
// (same as `cancelQueued`) rather than misrouting it.
|
|
406
426
|
queuedConversationIdRef.current = undefined;
|
|
407
|
-
|
|
427
|
+
setQueuedTurn(null);
|
|
408
428
|
return;
|
|
409
429
|
}
|
|
410
|
-
|
|
411
|
-
void
|
|
412
|
-
}, [options.conversationId,
|
|
430
|
+
setQueuedTurn(null);
|
|
431
|
+
void sendGuardedPrompt({ redaction: queuedTurn }, {});
|
|
432
|
+
}, [options.conversationId, queuedTurn, sendBlocker, sendGuardedPrompt]);
|
|
413
433
|
const reset = useCallback(() => {
|
|
414
434
|
attachmentGenerationRef.current += 1;
|
|
415
435
|
attachmentBatchIdRef.current = createAttachmentBatchId();
|
|
@@ -428,9 +448,10 @@ export function useChatPane(options) {
|
|
|
428
448
|
// `conversation.cancel()` above clears the streaming blocker — the flush effect fires it into
|
|
429
449
|
// the just-reset conversation instead of discarding it like the rest of this turn's state.
|
|
430
450
|
queuedConversationIdRef.current = undefined;
|
|
431
|
-
|
|
451
|
+
setQueuedTurn(null);
|
|
432
452
|
}, [composer, conversation, options.initialMessages]);
|
|
433
453
|
return {
|
|
454
|
+
secretRedacted: guard.secretRedacted,
|
|
434
455
|
conversation,
|
|
435
456
|
composer,
|
|
436
457
|
selection,
|
|
@@ -446,7 +467,7 @@ export function useChatPane(options) {
|
|
|
446
467
|
addAttachments,
|
|
447
468
|
send,
|
|
448
469
|
sendPrompt,
|
|
449
|
-
queuedPrompt,
|
|
470
|
+
queuedPrompt: queuedTurn?.text ?? null,
|
|
450
471
|
cancelQueued,
|
|
451
472
|
typedAnswerNotice,
|
|
452
473
|
sendAsNewMessage,
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { I18nAdapter } from '../../slots.js';
|
|
2
|
+
import type { ChatPaneAgent, ChatPaneAgentOption } from './types.js';
|
|
3
|
+
export declare function isConcretePickerModel(id: string): boolean;
|
|
4
|
+
/** Aliases are selectable only as their evidence-backed concrete ID. Never render a Default row.
|
|
5
|
+
* @complexity O(n) time and space in catalog rows, with set-based deduplication.
|
|
6
|
+
*/
|
|
7
|
+
export declare function pickerModelOptions(agent: ChatPaneAgent): ChatPaneAgentOption[];
|
|
8
|
+
export declare function configuredPickerModel(agent: ChatPaneAgent): string | undefined;
|
|
9
|
+
export declare function pickerModelLabel(agent: ChatPaneAgent | undefined, requested?: string, t?: I18nAdapter['t']): string;
|
|
10
|
+
export declare function pickerModelValue(agent: ChatPaneAgent, requested?: string): string;
|
|
11
|
+
export declare function pickerCatalogNote(agent: ChatPaneAgent, t?: I18nAdapter['t']): string | undefined;
|
|
12
|
+
//# sourceMappingURL=model-options.d.ts.map
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
// Model names and timestamps are metadata; only surrounding picker copy is translated.
|
|
2
|
+
const passthrough = key => key;
|
|
3
|
+
export function isConcretePickerModel(id) {
|
|
4
|
+
const token = id.slice(id.lastIndexOf('/') + 1);
|
|
5
|
+
return !/^(?:opus|sonnet|haiku|fable|best|latest|claude|gemini|gpt|codex|swe)(?:\[.*\])?$/i.test(token) && !/^(default|auto|adaptive|smart|deep|rush|ultimate|premium|balanced|fast|grok-build|vercel-ai-gateway)$/i.test(token) && !/(?:^|[-/])(gateway|latest)$/i.test(id);
|
|
6
|
+
}
|
|
7
|
+
/** Aliases are selectable only as their evidence-backed concrete ID. Never render a Default row.
|
|
8
|
+
* @complexity O(n) time and space in catalog rows, with set-based deduplication.
|
|
9
|
+
*/
|
|
10
|
+
export function pickerModelOptions(agent) {
|
|
11
|
+
if (agent.supportsConcreteModelSelection === false)
|
|
12
|
+
return [];
|
|
13
|
+
const seen = new Set();
|
|
14
|
+
const options = (agent.models || []).flatMap((row) => {
|
|
15
|
+
const id = row.resolvedId || (row.identityKind === 'routing-mode' || row.identityKind === 'alias' ? undefined : row.id);
|
|
16
|
+
if (!id || !isConcretePickerModel(id) || seen.has(id))
|
|
17
|
+
return [];
|
|
18
|
+
seen.add(id);
|
|
19
|
+
// Older cached inventories can attach a default-policy label to a resolved concrete ID.
|
|
20
|
+
const label = row.id === 'default' || /^default\b/i.test(row.label) ? id : row.label;
|
|
21
|
+
return [{ ...row, id, label, identityKind: 'concrete' }];
|
|
22
|
+
});
|
|
23
|
+
const resolution = agent.defaultModelResolution;
|
|
24
|
+
// A host may publish a resolved default outside its visible catalog. Preserve its concrete ID.
|
|
25
|
+
if (resolution?.status === 'resolved' && isConcretePickerModel(resolution.id) && !seen.has(resolution.id))
|
|
26
|
+
options.unshift({ id: resolution.id, label: resolution.id, identityKind: 'concrete' });
|
|
27
|
+
return options;
|
|
28
|
+
}
|
|
29
|
+
export function configuredPickerModel(agent) {
|
|
30
|
+
if (agent.supportsConcreteModelSelection === false)
|
|
31
|
+
return undefined;
|
|
32
|
+
const resolution = agent.defaultModelResolution;
|
|
33
|
+
return resolution?.status === 'resolved' && isConcretePickerModel(resolution.id) ? resolution.id : undefined;
|
|
34
|
+
}
|
|
35
|
+
export function pickerModelLabel(agent, requested, t = passthrough) {
|
|
36
|
+
if (!agent)
|
|
37
|
+
return t('Choose model');
|
|
38
|
+
const id = !requested || requested === 'default' ? configuredPickerModel(agent) : requested;
|
|
39
|
+
if (!id || !isConcretePickerModel(id))
|
|
40
|
+
return t('Choose model');
|
|
41
|
+
return pickerModelOptions(agent).find((row) => row.id === id)?.label || id;
|
|
42
|
+
}
|
|
43
|
+
export function pickerModelValue(agent, requested) {
|
|
44
|
+
const id = requested && requested !== 'default' ? requested : configuredPickerModel(agent);
|
|
45
|
+
return id && pickerModelOptions(agent).some((row) => row.id === id) ? id : '';
|
|
46
|
+
}
|
|
47
|
+
export function pickerCatalogNote(agent, t = passthrough) {
|
|
48
|
+
const catalog = agent.modelCatalog;
|
|
49
|
+
if (catalog?.freshness === 'stale')
|
|
50
|
+
return `${t('Cached')} · ${catalog.fetchedAt}`;
|
|
51
|
+
if (catalog?.freshness === 'offline-fallback' || !catalog && agent.models)
|
|
52
|
+
return t('Offline fallback');
|
|
53
|
+
return undefined;
|
|
54
|
+
}
|
|
55
|
+
//# sourceMappingURL=model-options.js.map
|
|
@@ -14,9 +14,10 @@ export declare function resolveChatPaneSelection({ agents, requested }: {
|
|
|
14
14
|
* precondition (`composer.canSubmit`), not a property of the pane. An agent-driven send supplies
|
|
15
15
|
* its own prompt and must still clear every blocker below.
|
|
16
16
|
*/
|
|
17
|
-
export type ChatPaneSendBlocker = 'no-agent-selected' | 'agent-unavailable' | 'streaming' | 'uploads-pending' | 'working-directory-pending' | 'working-directory-invalid' | 'working-directory-error';
|
|
17
|
+
export type ChatPaneSendBlocker = 'no-agent-selected' | 'agent-unavailable' | 'model-unresolved' | 'streaming' | 'uploads-pending' | 'working-directory-pending' | 'working-directory-invalid' | 'working-directory-error';
|
|
18
18
|
export interface ChatPaneSendability {
|
|
19
|
-
readonly selectedAgent: Pick<ChatPaneAgent, 'available'> | undefined;
|
|
19
|
+
readonly selectedAgent: Pick<ChatPaneAgent, 'available' | 'modelCatalog' | 'supportsConcreteModelSelection'> | undefined;
|
|
20
|
+
readonly model?: string;
|
|
20
21
|
readonly isStreaming: boolean;
|
|
21
22
|
readonly activeUploadCount: number;
|
|
22
23
|
readonly workingDirectoryPending: boolean;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { configuredPickerModel, pickerModelOptions, isConcretePickerModel } from './model-options.js';
|
|
1
2
|
function preferredOptionId(options) {
|
|
2
3
|
return options?.find((option) => option.id === 'default')?.id ?? options?.[0]?.id;
|
|
3
4
|
}
|
|
@@ -7,7 +8,9 @@ function validatedOptionId(options, requested, fallback) {
|
|
|
7
8
|
return fallback;
|
|
8
9
|
}
|
|
9
10
|
export function defaultChatPaneSelection({ agent }) {
|
|
10
|
-
|
|
11
|
+
// An unresolved native route requires an intentional concrete choice. Legacy hosts without
|
|
12
|
+
// structured resolution retain their first-concrete-entry behavior.
|
|
13
|
+
const model = configuredPickerModel(agent) || (agent.defaultModelResolution?.status === 'unresolved' ? undefined : pickerModelOptions(agent)[0]?.id);
|
|
11
14
|
const reasoning = preferredOptionId(agent.reasoningOptions);
|
|
12
15
|
return {
|
|
13
16
|
agentId: agent.id,
|
|
@@ -16,17 +19,19 @@ export function defaultChatPaneSelection({ agent }) {
|
|
|
16
19
|
};
|
|
17
20
|
}
|
|
18
21
|
export function resolveChatPaneSelection({ agents, requested }) {
|
|
19
|
-
const requestedAgent = agents.find((agent) => agent.id === requested.agentId && agent.available !== false);
|
|
20
|
-
const agent = requestedAgent ?? agents.find((candidate) => candidate.available !== false);
|
|
22
|
+
const requestedAgent = agents.find((agent) => agent.id === requested.agentId && agent.id !== 'gemini' && agent.available !== false);
|
|
23
|
+
const agent = requestedAgent ?? agents.find((candidate) => candidate.id !== 'gemini' && candidate.available !== false);
|
|
21
24
|
if (!agent)
|
|
22
25
|
return { agentId: '' };
|
|
23
26
|
const defaults = defaultChatPaneSelection({ agent: agent });
|
|
24
27
|
if (agent !== requestedAgent)
|
|
25
28
|
return defaults;
|
|
26
29
|
const model = requested.model
|
|
30
|
+
&& isConcretePickerModel(requested.model)
|
|
27
31
|
&& agent.supportsCustomModel
|
|
32
|
+
&& agent.supportsConcreteModelSelection !== false
|
|
28
33
|
? requested.model
|
|
29
|
-
: validatedOptionId(agent
|
|
34
|
+
: validatedOptionId(pickerModelOptions(agent), requested.model, defaults.model);
|
|
30
35
|
const reasoning = validatedOptionId(agent.reasoningOptions, requested.reasoning, defaults.reasoning);
|
|
31
36
|
return {
|
|
32
37
|
agentId: agent.id,
|
|
@@ -64,6 +69,8 @@ export function findChatPaneSendBlocker(state) {
|
|
|
64
69
|
else if (state.selectedAgent.available === false) {
|
|
65
70
|
return 'agent-unavailable';
|
|
66
71
|
}
|
|
72
|
+
if (!state.apiModeConfigured && state.selectedAgent && (state.selectedAgent.modelCatalog || state.selectedAgent.supportsConcreteModelSelection === false) && (!state.model || !isConcretePickerModel(state.model) || state.selectedAgent.supportsConcreteModelSelection === false))
|
|
73
|
+
return 'model-unresolved';
|
|
67
74
|
if (state.isStreaming)
|
|
68
75
|
return 'streaming';
|
|
69
76
|
if (state.activeUploadCount > 0)
|
|
@@ -79,6 +86,7 @@ export function findChatPaneSendBlocker(state) {
|
|
|
79
86
|
const SEND_BLOCKER_MESSAGES = {
|
|
80
87
|
'no-agent-selected': 'no agent is selected',
|
|
81
88
|
'agent-unavailable': 'the selected agent is unavailable',
|
|
89
|
+
'model-unresolved': 'choose a concrete model before sending',
|
|
82
90
|
streaming: 'a run is already streaming — cancel it first',
|
|
83
91
|
'uploads-pending': 'attachment uploads are still in flight',
|
|
84
92
|
'working-directory-pending': 'the working directory is still being validated',
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { UserTextRedactionOptions } from '../../../core/user-text-redaction.js';
|
|
1
2
|
import type { CSSProperties, ReactNode, RefObject } from 'react';
|
|
2
3
|
import type { ChatAttachment, ChatMessage } from '@jini-ai/chat';
|
|
3
4
|
import type { ComposerSlots } from '../../slots.js';
|
|
@@ -6,6 +7,8 @@ import type { ChatTransport, RunContext } from '@jini-ai/chat';
|
|
|
6
7
|
export interface ChatPaneAgentOption {
|
|
7
8
|
id: string;
|
|
8
9
|
label: string;
|
|
10
|
+
identityKind?: 'concrete' | 'alias' | 'routing-mode';
|
|
11
|
+
resolvedId?: string;
|
|
9
12
|
}
|
|
10
13
|
/**
|
|
11
14
|
* Browser-safe runtime inventory consumed by the chat pane. This is
|
|
@@ -19,8 +22,29 @@ export interface ChatPaneAgent {
|
|
|
19
22
|
version?: string | null;
|
|
20
23
|
authStatus?: 'ok' | 'missing' | 'unknown';
|
|
21
24
|
models?: readonly ChatPaneAgentOption[];
|
|
25
|
+
modelCatalog?: {
|
|
26
|
+
source: 'cli' | 'rpc' | 'provider-api' | 'cli-cache' | 'config-file' | 'offline-fallback';
|
|
27
|
+
freshness: 'fresh' | 'stale' | 'offline-fallback';
|
|
28
|
+
fetchedAt: string;
|
|
29
|
+
expiresAt: string;
|
|
30
|
+
coverage: 'account' | 'provider' | 'configured' | 'observed';
|
|
31
|
+
launchFingerprint: string;
|
|
32
|
+
};
|
|
33
|
+
defaultModelResolution?: {
|
|
34
|
+
status: 'resolved';
|
|
35
|
+
id: string;
|
|
36
|
+
selectionId?: string;
|
|
37
|
+
source: string;
|
|
38
|
+
resolvedAt: string;
|
|
39
|
+
launchFingerprint: string;
|
|
40
|
+
} | {
|
|
41
|
+
status: 'unresolved';
|
|
42
|
+
selectionId?: string;
|
|
43
|
+
reason: string;
|
|
44
|
+
};
|
|
22
45
|
reasoningOptions?: readonly ChatPaneAgentOption[];
|
|
23
46
|
supportsCustomModel?: boolean;
|
|
47
|
+
supportsConcreteModelSelection?: boolean;
|
|
24
48
|
diagnostic?: string;
|
|
25
49
|
/**
|
|
26
50
|
* Whether this runtime can receive external MCP servers (host/Jini tools) at all — mirrors
|
|
@@ -143,7 +167,7 @@ export type DeliverTypedAnswer = ((input: {
|
|
|
143
167
|
}) => Promise<TypedAnswerDelivery>) & {
|
|
144
168
|
readonly toolName: string;
|
|
145
169
|
};
|
|
146
|
-
export interface ChatPaneProps {
|
|
170
|
+
export interface ChatPaneProps extends UserTextRedactionOptions {
|
|
147
171
|
transport: ChatTransport;
|
|
148
172
|
agents?: readonly ChatPaneAgent[];
|
|
149
173
|
/** Optional host effects for package-owned inventory, rescan, and health polling. */
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import type { ChatMessage } from "../../core/messages.js";
|
|
2
|
+
import type { AssistantConversation } from "../../core/assistant-client/messages.js";
|
|
3
|
+
/**
|
|
4
|
+
* @file What `useAssistantChats` needs from the outside world, as an interface rather than a set of
|
|
5
|
+
* module imports.
|
|
6
|
+
*
|
|
7
|
+
* The hook used to import `lib/assistant-chats`'s functions directly, which meant the only way to
|
|
8
|
+
* test it was to stub the global `fetch` and assert on the URLs and methods that came out — the
|
|
9
|
+
* tests described HTTP when what they meant to describe was "which conversation did this message
|
|
10
|
+
* get written to". Worse, behaviour that has nothing to do with transport (the write retry and its
|
|
11
|
+
* backoff) could only be exercised by hand-building `Response` objects with the right status codes.
|
|
12
|
+
*
|
|
13
|
+
* Declaring the seam here follows the `useX(dependencies)` / `useWiredX()` pair this workspace
|
|
14
|
+
* already uses throughout `@jini-ai/ui` (see `features/html-viewer/react/hooks/usePresentMode.ts`
|
|
15
|
+
* for the canonical shape, and `foundry/docs/jini-port/skills/fixing-open-design-web.md` for the
|
|
16
|
+
* rules). `ports.ts` declares, `assistant-chats-dependencies.ts` binds, and nothing else imports the
|
|
17
|
+
* real provider.
|
|
18
|
+
*
|
|
19
|
+
* `AssistantConversation` is imported here `import type`, which is erased at compile time — so this
|
|
20
|
+
* file still has no runtime dependency on `lib/assistant-chats`, and `dependencies.ts` remains the
|
|
21
|
+
* single place the real implementation is reached.
|
|
22
|
+
*
|
|
23
|
+
* ## What is deliberately NOT in this port
|
|
24
|
+
*
|
|
25
|
+
* `persistableMessages` and `HttpError`. The first is a pure rule — no I/O, no host — and per the
|
|
26
|
+
* pattern a hook imports rules directly rather than having them injected; making it swappable would
|
|
27
|
+
* let a fake quietly change which messages count as settled, which is the one thing every test here
|
|
28
|
+
* needs to hold still. The second is the error type an implementation is expected to throw for a
|
|
29
|
+
* non-2xx response, and it is part of this contract even though it is not a method: {@link
|
|
30
|
+
* AssistantChatsPort.saveMessage}'s retry classifies failures by it.
|
|
31
|
+
*/
|
|
32
|
+
export interface AssistantChatsPort {
|
|
33
|
+
listConversations(required: Record<string, never>, optional: Record<string, never>): Promise<AssistantConversation[]>;
|
|
34
|
+
/** `firstMessage` seeds the server-side title heuristic; omit it for an untitled empty chat. */
|
|
35
|
+
createConversation(required: {
|
|
36
|
+
firstMessage?: string;
|
|
37
|
+
}, optional: Record<string, never>): Promise<AssistantConversation>;
|
|
38
|
+
renameConversation(required: {
|
|
39
|
+
id: string;
|
|
40
|
+
title: string;
|
|
41
|
+
}, optional: Record<string, never>): Promise<void>;
|
|
42
|
+
deleteConversation(required: {
|
|
43
|
+
id: string;
|
|
44
|
+
}, optional: Record<string, never>): Promise<void>;
|
|
45
|
+
loadMessages(required: {
|
|
46
|
+
conversationId: string;
|
|
47
|
+
}, optional: Record<string, never>): Promise<ChatMessage[]>;
|
|
48
|
+
/**
|
|
49
|
+
* Writes one message.
|
|
50
|
+
*
|
|
51
|
+
* Expected to reject with an `HttpError` for a non-2xx response and with anything else for a
|
|
52
|
+
* transport failure — the hook retries the second kind, and the first only for 429/5xx. An
|
|
53
|
+
* implementation that swallows failures into a resolved promise silently disables the retry.
|
|
54
|
+
*/
|
|
55
|
+
saveMessage(required: {
|
|
56
|
+
conversationId: string;
|
|
57
|
+
message: ChatMessage;
|
|
58
|
+
}, optional: Record<string, never>): Promise<void>;
|
|
59
|
+
}
|
|
60
|
+
export interface UseAssistantChats {
|
|
61
|
+
conversations: AssistantConversation[];
|
|
62
|
+
activeId: string | null;
|
|
63
|
+
/**
|
|
64
|
+
* `ChatPane`'s `key`. Deliberately NOT `activeId`.
|
|
65
|
+
*
|
|
66
|
+
* Remounting the pane is correct for a user-initiated switch and destructive at any other time: it
|
|
67
|
+
* re-seeds from `initialMessages` and discards whatever the pane currently holds. `activeId` now
|
|
68
|
+
* also changes when an untitled pane silently adopts a freshly created conversation mid-run (see
|
|
69
|
+
* `onMessagesChange`), and re-keying on that would wipe the reply being streamed. So the two are
|
|
70
|
+
* separate: this changes only on `select`/`create`/`remove`, `activeId` tracks where writes go.
|
|
71
|
+
*/
|
|
72
|
+
paneKey: string;
|
|
73
|
+
/** Messages to seed the pane with. Changes identity only on a real conversation switch. */
|
|
74
|
+
initialMessages: ChatMessage[];
|
|
75
|
+
select: (required: {
|
|
76
|
+
id: string;
|
|
77
|
+
}, optional?: Record<string, never>) => void;
|
|
78
|
+
create: (required: Record<string, never>, optional?: Record<string, never>) => Promise<void>;
|
|
79
|
+
remove: (required: {
|
|
80
|
+
id: string;
|
|
81
|
+
}, optional?: Record<string, never>) => Promise<void>;
|
|
82
|
+
rename: (required: {
|
|
83
|
+
id: string;
|
|
84
|
+
title: string;
|
|
85
|
+
}, optional?: Record<string, never>) => Promise<void>;
|
|
86
|
+
onMessagesChange: (required: {
|
|
87
|
+
messages: ChatMessage[];
|
|
88
|
+
}, optional?: Record<string, never>) => void;
|
|
89
|
+
/**
|
|
90
|
+
* This pane's conversation id, adopting one if none is active yet. Awaited by
|
|
91
|
+
* `assistant-transport.ts`'s `startRun` so turn 1's run carries the identity its agent-CLI session
|
|
92
|
+
* id gets filed under — see the hook's own implementation doc for the amnesia defect that needs it.
|
|
93
|
+
* Resolves `null` (never rejects) when creation fails.
|
|
94
|
+
*/
|
|
95
|
+
ensureConversationId: (required: Record<string, never>, optional?: Record<string, never>) => Promise<string | null>;
|
|
96
|
+
/**
|
|
97
|
+
* Writes one user turn durably, awaited by `assistant-transport.ts`'s `startRun` BEFORE it
|
|
98
|
+
* dispatches the run — see the implementation's own doc for the defect (a failed run's user
|
|
99
|
+
* message reaching no agent and no later prompt). Never rejects; a failed write releases the id
|
|
100
|
+
* so the delta-driven `flush` can still pick it up.
|
|
101
|
+
*/
|
|
102
|
+
persistUserTurn: (required: {
|
|
103
|
+
conversationId: string;
|
|
104
|
+
message: ChatMessage;
|
|
105
|
+
}, optional?: Record<string, never>) => Promise<void>;
|
|
106
|
+
}
|
|
107
|
+
export interface AssistantChatsClientPorts {
|
|
108
|
+
readonly statusOf: (required: {
|
|
109
|
+
error: unknown;
|
|
110
|
+
}, optional: Record<string, never>) => number | undefined;
|
|
111
|
+
readonly sleep: (required: {
|
|
112
|
+
delayMs: number;
|
|
113
|
+
}, optional: Record<string, never>) => Promise<void>;
|
|
114
|
+
readonly reportPermanent: (required: {
|
|
115
|
+
conversationId: string;
|
|
116
|
+
messageId: string;
|
|
117
|
+
error: unknown;
|
|
118
|
+
}, optional: Record<string, never>) => void;
|
|
119
|
+
readonly requestRunPrefixes: readonly string[];
|
|
120
|
+
}
|
|
121
|
+
export type SaveOutcome = "saved" | "exhausted" | "permanent" | "missing";
|
|
122
|
+
export type AttemptResult = {
|
|
123
|
+
done: true;
|
|
124
|
+
outcome: SaveOutcome;
|
|
125
|
+
} | {
|
|
126
|
+
done: false;
|
|
127
|
+
};
|
|
128
|
+
export interface SaveFailureInput {
|
|
129
|
+
error: unknown;
|
|
130
|
+
conversationId: string;
|
|
131
|
+
message: ChatMessage;
|
|
132
|
+
attempt: number;
|
|
133
|
+
isDisposed: () => boolean;
|
|
134
|
+
}
|
|
135
|
+
export interface AssistantChatsClient {
|
|
136
|
+
giveUpOutcome(required: {
|
|
137
|
+
error: unknown;
|
|
138
|
+
}, optional?: Record<string, never>): SaveOutcome;
|
|
139
|
+
handleSaveFailure(required: SaveFailureInput, optional?: Record<string, never>): Promise<AttemptResult>;
|
|
140
|
+
attemptSave(required: Omit<SaveFailureInput, "error"> & {
|
|
141
|
+
port: AssistantChatsPort;
|
|
142
|
+
}, optional?: Record<string, never>): Promise<AttemptResult>;
|
|
143
|
+
summarizeFlushOutcomes(required: {
|
|
144
|
+
results: readonly {
|
|
145
|
+
message: ChatMessage;
|
|
146
|
+
outcome: SaveOutcome;
|
|
147
|
+
}[];
|
|
148
|
+
isConversationStillActive: boolean;
|
|
149
|
+
}, optional?: Record<string, never>): {
|
|
150
|
+
idsToRelease: string[];
|
|
151
|
+
shouldRefresh: boolean;
|
|
152
|
+
};
|
|
153
|
+
resolveRememberedConversation(required: {
|
|
154
|
+
remembered: string | null;
|
|
155
|
+
conversations: readonly AssistantConversation[];
|
|
156
|
+
listFailed: boolean;
|
|
157
|
+
}, optional?: Record<string, never>): "restore" | "forget" | "keep";
|
|
158
|
+
useAssistantChats(required: {
|
|
159
|
+
port: AssistantChatsPort;
|
|
160
|
+
}, optional?: {
|
|
161
|
+
lastConversation?: import("./last-conversation-store.js").LastConversationStore;
|
|
162
|
+
}): UseAssistantChats;
|
|
163
|
+
}
|
|
164
|
+
//# sourceMappingURL=assistant-chats-contracts.d.ts.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { AssistantChatsClientPorts, AssistantChatsClient } from "./assistant-chats-contracts.js";
|
|
2
|
+
/** Bind effects once; the returned hook captures its conversation port at mount.
|
|
3
|
+
* Existing race guards and retry ordering remain in the original lexical lifecycle. */
|
|
4
|
+
export declare function createAssistantChatsClient({ ports }: {
|
|
5
|
+
ports: AssistantChatsClientPorts;
|
|
6
|
+
}, _options?: Record<string, never>): AssistantChatsClient;
|
|
7
|
+
//# sourceMappingURL=assistant-chats.d.ts.map
|