@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
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Restore cached attachment references only after the host confirms their bytes still exist.
|
|
3
|
+
* Drafts persist references separately from text; staged bytes have a host-defined TTL. Dropping
|
|
4
|
+
* an unconfirmed live reference costs a re-drag, while restoring a dead one causes a failed turn.
|
|
5
|
+
* Ref shape, endpoint and probe cap enter through host ports: malformed/excess refs, refusals,
|
|
6
|
+
* network failures and timeouts are discarded, without losing the draft's words.
|
|
7
|
+
* HEAD avoids downloading every attachment to answer a liveness question. The host endpoint must
|
|
8
|
+
* support it (Express routes HEAD through a GET handler when no HEAD handler exists and omits the
|
|
9
|
+
* body); get-chat-attachment-route.test.ts covers the original composed route.
|
|
10
|
+
*/
|
|
11
|
+
/** In-flight probes. Matches `createDaemonAttachmentUploader`'s own upload concurrency — enough to
|
|
12
|
+
* hide localhost latency, few enough to leave the page's connection budget alone. */
|
|
13
|
+
const DEFAULT_CONCURRENCY = 2;
|
|
14
|
+
/** Abort deadline per probe. A restore runs at mount behind the composer, so a wedged request must
|
|
15
|
+
* not keep a worker (and the operator's attachment chips) pending indefinitely. */
|
|
16
|
+
const DEFAULT_TIMEOUT_MS = 10_000;
|
|
17
|
+
/**
|
|
18
|
+
* Whether the server still serves this one reference.
|
|
19
|
+
*
|
|
20
|
+
* @param ref opaque `attachment:<uuid>` capability id (`ChatAttachment.path`).
|
|
21
|
+
* @returns `true` only on a 2xx. Every refusal and every fault answers `false` — the caller cannot
|
|
22
|
+
* act differently on them, and a probe that could not reach the server has not confirmed anything.
|
|
23
|
+
* @complexity O(1); one request, abandoned after `timeoutMs`.
|
|
24
|
+
*/
|
|
25
|
+
async function isAttachmentStillServed(ref, options) {
|
|
26
|
+
try {
|
|
27
|
+
const response = await options.fetchImpl(options.endpoint({ ref }, {}), {
|
|
28
|
+
method: "HEAD",
|
|
29
|
+
credentials: "same-origin",
|
|
30
|
+
signal: AbortSignal.timeout(options.timeoutMs),
|
|
31
|
+
});
|
|
32
|
+
return response.ok;
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
// A network fault, an abort, or a same-origin policy refusal. Indistinguishable from here, and
|
|
36
|
+
// all mean the same thing: nothing was confirmed, so the reference does not come back.
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Runs `probe` over `refs` with at most `concurrency` in flight, answering positionally.
|
|
42
|
+
*
|
|
43
|
+
* Shared-iterator workers rather than a chunked `Promise.all` loop, for the same reason
|
|
44
|
+
* `createDaemonAttachmentUploader` uses them: a chunked loop stalls the whole batch on its slowest
|
|
45
|
+
* member, and this one runs while the operator is looking at a half-restored composer.
|
|
46
|
+
*
|
|
47
|
+
* @complexity O(n) requests, at most `concurrency` concurrent; O(n) space for the verdicts.
|
|
48
|
+
*/
|
|
49
|
+
async function probeAll(refs, concurrency, probe) {
|
|
50
|
+
const verdicts = new Array(refs.length).fill(false);
|
|
51
|
+
const entries = refs.entries();
|
|
52
|
+
async function worker() {
|
|
53
|
+
for (const [index, ref] of entries) {
|
|
54
|
+
verdicts[index] = await probe(ref);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
await Promise.all(Array.from({ length: Math.max(1, Math.min(concurrency, refs.length)) }, worker));
|
|
58
|
+
return verdicts;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Builds the `validateAttachments` callback `<ChatPane>` calls when it restores a persisted draft.
|
|
62
|
+
*
|
|
63
|
+
* @param input.fetch injected request implementation; the host supplies it explicitly.
|
|
64
|
+
* @param input.endpoint resolves a reference to the host's HEAD-capable read endpoint.
|
|
65
|
+
* @param input.isSupportedRef validates the host's staged-reference grammar before a request.
|
|
66
|
+
* @param input.maxProbedAttachments bounds request fan-out from malformed or hand-edited drafts.
|
|
67
|
+
* @param options.concurrency in-flight probe cap; defaults to {@link DEFAULT_CONCURRENCY}.
|
|
68
|
+
* @param options.timeoutMs per-probe abort deadline; defaults to {@link DEFAULT_TIMEOUT_MS}.
|
|
69
|
+
* @returns a validator taking the cached references and resolving to the subset still served, in
|
|
70
|
+
* the order they were given. Never rejects: a caller's only sane response to a rejection is to
|
|
71
|
+
* restore nothing, which is what an empty subset already means.
|
|
72
|
+
* @complexity O(n) requests bounded by input.maxProbedAttachments, at most concurrency at once.
|
|
73
|
+
* @example
|
|
74
|
+
* <ChatPane validateAttachments={createChatAttachmentValidator({ fetch, endpoint, isSupportedRef, maxProbedAttachments })} />
|
|
75
|
+
*/
|
|
76
|
+
export function createChatAttachmentValidator({ fetch, endpoint, isSupportedRef, maxProbedAttachments }, options = {}) {
|
|
77
|
+
const probeOptions = {
|
|
78
|
+
fetchImpl: fetch,
|
|
79
|
+
endpoint,
|
|
80
|
+
timeoutMs: options.timeoutMs ?? DEFAULT_TIMEOUT_MS,
|
|
81
|
+
};
|
|
82
|
+
const concurrency = options.concurrency ?? DEFAULT_CONCURRENCY;
|
|
83
|
+
return async (attachments) => {
|
|
84
|
+
const candidates = attachments
|
|
85
|
+
.slice(0, maxProbedAttachments)
|
|
86
|
+
.filter((candidate) => isSupportedRef({ ref: candidate.path }, {}));
|
|
87
|
+
if (candidates.length === 0)
|
|
88
|
+
return [];
|
|
89
|
+
const verdicts = await probeAll(candidates.map((candidate) => candidate.path), concurrency, (ref) => isAttachmentStillServed(ref, probeOptions));
|
|
90
|
+
return candidates.filter((_, index) => verdicts[index]);
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
//# sourceMappingURL=attachment-liveness.js.map
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { ChatMessage } from "../messages.js";
|
|
2
|
+
import type { RunHandlers } from "../transport.js";
|
|
3
|
+
import type { RunNotices } from "../run-events/entry.js";
|
|
4
|
+
export interface DurableRunBinding {
|
|
5
|
+
readonly messageId: string;
|
|
6
|
+
readonly conversationId: string;
|
|
7
|
+
}
|
|
8
|
+
export interface DurableSubscriptionPorts {
|
|
9
|
+
readonly read: (required: {
|
|
10
|
+
runId: string;
|
|
11
|
+
messageId?: string;
|
|
12
|
+
}, optional: {}) => Promise<ChatMessage | null | "gone">;
|
|
13
|
+
readonly stream: (required: {
|
|
14
|
+
runId: string;
|
|
15
|
+
cursor: string;
|
|
16
|
+
frame: (kind: string, data: string, cursor: string) => void;
|
|
17
|
+
dropped: () => void;
|
|
18
|
+
}, optional: {}) => () => void;
|
|
19
|
+
readonly schedule: (required: {
|
|
20
|
+
work: () => void;
|
|
21
|
+
delayMs: number;
|
|
22
|
+
}, optional: {}) => () => void;
|
|
23
|
+
}
|
|
24
|
+
type CheckpointHandlers = RunHandlers & {
|
|
25
|
+
onCheckpoint?: (message: ChatMessage) => void;
|
|
26
|
+
};
|
|
27
|
+
/** SSE is a view of one attempt. The saved message owns terminal state and can point at a new
|
|
28
|
+
* attempt after recovery; neither a browser 404 nor a closed stream may fail the logical run. */
|
|
29
|
+
export declare function followDurableRun({ runId: initialRunId, handlers, signal, binding, ports, notices, bindings }: {
|
|
30
|
+
runId: string;
|
|
31
|
+
handlers: CheckpointHandlers;
|
|
32
|
+
signal?: AbortSignal;
|
|
33
|
+
binding?: DurableRunBinding;
|
|
34
|
+
ports: DurableSubscriptionPorts;
|
|
35
|
+
notices: RunNotices;
|
|
36
|
+
bindings: Map<string, DurableRunBinding>;
|
|
37
|
+
}, _optional?: {}): () => void;
|
|
38
|
+
export {};
|
|
39
|
+
//# sourceMappingURL=durable-run-subscription.d.ts.map
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { translateRunFrame } from "../run-events/entry.js";
|
|
2
|
+
/** SSE is a view of one attempt. The saved message owns terminal state and can point at a new
|
|
3
|
+
* attempt after recovery; neither a browser 404 nor a closed stream may fail the logical run. */
|
|
4
|
+
export function followDurableRun({ runId: initialRunId, handlers, signal, binding, ports, notices, bindings }, _optional = {}) {
|
|
5
|
+
let runId = initialRunId;
|
|
6
|
+
let cursor = "";
|
|
7
|
+
let stopped = false;
|
|
8
|
+
let reading = false;
|
|
9
|
+
let dropped = false;
|
|
10
|
+
let stopStream;
|
|
11
|
+
let stopTimer;
|
|
12
|
+
let messageId = binding?.messageId;
|
|
13
|
+
function stop() {
|
|
14
|
+
stopped = true;
|
|
15
|
+
stopStream?.();
|
|
16
|
+
stopTimer?.();
|
|
17
|
+
signal?.removeEventListener("abort", stop);
|
|
18
|
+
}
|
|
19
|
+
function finish(message) {
|
|
20
|
+
stop();
|
|
21
|
+
handlers.onCheckpoint?.(message);
|
|
22
|
+
if (message.runStatus === "failed" && !handlers.onCheckpoint)
|
|
23
|
+
handlers.onError(new Error("The assistant could not finish this answer. Saved work is above."));
|
|
24
|
+
handlers.onDone(message.events ?? []);
|
|
25
|
+
}
|
|
26
|
+
function open() {
|
|
27
|
+
dropped = false;
|
|
28
|
+
stopStream?.();
|
|
29
|
+
stopStream = ports.stream({ runId, cursor, frame, dropped: () => { dropped = true; void read().catch(() => undefined); } }, {});
|
|
30
|
+
}
|
|
31
|
+
function frame(kind, data, nextCursor) {
|
|
32
|
+
if (stopped)
|
|
33
|
+
return;
|
|
34
|
+
if (nextCursor)
|
|
35
|
+
cursor = nextCursor;
|
|
36
|
+
const outcome = translateRunFrame({ kind, raw: data, notices }, {});
|
|
37
|
+
// An attempt's error/end may be followed by a continuation. Only the row can finalize it.
|
|
38
|
+
if (outcome.terminal || outcome.error) {
|
|
39
|
+
void read().catch(() => undefined);
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
// With the checkpoint-capable hook, render only durable projections. Replaying a daemon
|
|
43
|
+
// prefix into a seeded bubble would duplicate its text; the server folds those deltas once.
|
|
44
|
+
if (handlers.onCheckpoint)
|
|
45
|
+
return;
|
|
46
|
+
for (const event of outcome.events)
|
|
47
|
+
handlers.onEvent(event);
|
|
48
|
+
}
|
|
49
|
+
async function read() {
|
|
50
|
+
if (stopped || reading)
|
|
51
|
+
return;
|
|
52
|
+
reading = true;
|
|
53
|
+
stopTimer?.();
|
|
54
|
+
stopTimer = undefined;
|
|
55
|
+
try {
|
|
56
|
+
const message = await ports.read({ runId, ...(messageId ? { messageId } : {}) }, {});
|
|
57
|
+
if (stopped || !message)
|
|
58
|
+
return;
|
|
59
|
+
if (message === "gone") {
|
|
60
|
+
stop();
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
applySavedMessage(message);
|
|
64
|
+
}
|
|
65
|
+
finally {
|
|
66
|
+
reading = false;
|
|
67
|
+
if (!stopped)
|
|
68
|
+
stopTimer = ports.schedule({ work: () => void read().catch(() => undefined), delayMs: 1_000 }, {});
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
function applySavedMessage(message) {
|
|
72
|
+
messageId = message.id;
|
|
73
|
+
if (["succeeded", "failed", "canceled"].includes(message.runStatus ?? "")) {
|
|
74
|
+
finish(message);
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
handlers.onCheckpoint?.(message);
|
|
78
|
+
if (message.runId && message.runId !== runId) {
|
|
79
|
+
runId = message.runId;
|
|
80
|
+
cursor = "";
|
|
81
|
+
if (binding)
|
|
82
|
+
bindings.set(runId, binding);
|
|
83
|
+
dropped = true;
|
|
84
|
+
}
|
|
85
|
+
if (dropped)
|
|
86
|
+
open();
|
|
87
|
+
}
|
|
88
|
+
if (signal?.aborted)
|
|
89
|
+
return stop;
|
|
90
|
+
signal?.addEventListener("abort", stop);
|
|
91
|
+
open();
|
|
92
|
+
// Polling also covers an attempt that dies before a first frame/connection error arrives.
|
|
93
|
+
stopTimer = ports.schedule({ work: () => void read().catch(() => undefined), delayMs: 1_000 }, {});
|
|
94
|
+
return stop;
|
|
95
|
+
}
|
|
96
|
+
//# sourceMappingURL=durable-run-subscription.js.map
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/** Daemon/BYOK/AG-UI chat client lifecycles. Host routes, identity and authenticated I/O are ports. */
|
|
2
|
+
export { createAssistantTransportClient } from "./transport.js";
|
|
3
|
+
export type { AssistantTransportPorts, AssistantTransportOptions, AssistantTransportClient, ByokFrameContext, ByokStreamContext } from "./transport.js";
|
|
4
|
+
export type { AgUiClient, AgUiClientPorts, AgUiRunClient, AgUiEventContext } from "./ag-ui-transport.js";
|
|
5
|
+
export { followDurableRun } from "./durable-run-subscription.js";
|
|
6
|
+
export type { DurableRunBinding, DurableSubscriptionPorts } from "./durable-run-subscription.js";
|
|
7
|
+
export { activeRunStub, messageWriteKey, persistableMessages } from "./messages.js";
|
|
8
|
+
export type { AssistantConversation } from "./messages.js";
|
|
9
|
+
export { createChatAttachmentValidator } from "./attachment-liveness.js";
|
|
10
|
+
export type { ChatAttachmentValidatorInput, ChatAttachmentValidatorOptions } from "./attachment-liveness.js";
|
|
11
|
+
export { createA2uiActionPoster } from "./a2ui-action-poster.js";
|
|
12
|
+
export type { A2uiActionPosterInput, CreateA2uiActionPosterOptions, A2uiActionDeliveryOutcome } from "./a2ui-action-poster.js";
|
|
13
|
+
//# sourceMappingURL=entry.d.ts.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/** Daemon/BYOK/AG-UI chat client lifecycles. Host routes, identity and authenticated I/O are ports. */
|
|
2
|
+
export { createAssistantTransportClient } from "./transport.js";
|
|
3
|
+
export { followDurableRun } from "./durable-run-subscription.js";
|
|
4
|
+
export { activeRunStub, messageWriteKey, persistableMessages } from "./messages.js";
|
|
5
|
+
export { createChatAttachmentValidator } from "./attachment-liveness.js";
|
|
6
|
+
export { createA2uiActionPoster } from "./a2ui-action-poster.js";
|
|
7
|
+
//# sourceMappingURL=entry.js.map
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import type { ChatMessage } from "../messages.js";
|
|
2
|
+
export interface AssistantConversation {
|
|
3
|
+
id: string;
|
|
4
|
+
title: string | null;
|
|
5
|
+
titleSource: "fallback" | "generated" | "manual";
|
|
6
|
+
messageCount: number;
|
|
7
|
+
createdAt: number;
|
|
8
|
+
updatedAt: number;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* The subset of a live transcript that is worth writing: user turns, and assistant turns whose run
|
|
12
|
+
* has reached a terminal status.
|
|
13
|
+
*
|
|
14
|
+
* An assistant message that is still `queued` or `running` is rewritten on every delta, so
|
|
15
|
+
* persisting it would turn one reply into hundreds of writes for a row that is about to be
|
|
16
|
+
* replaced anyway.
|
|
17
|
+
*
|
|
18
|
+
* A `failed`/`canceled` assistant turn with EMPTY content is kept deliberately, and this is the
|
|
19
|
+
* place a future "stop writing those, they are just noise" change would land — don't (2026-09-11
|
|
20
|
+
* chat-lifecycle repair, Defect 3). The row's `events_json` is the only durable record of why the
|
|
21
|
+
* run died: the daemon's own event log is in-memory and gone with the process, so the stored
|
|
22
|
+
* `"Run failed — the agent process exited without answering"` notice with its exit code and signal
|
|
23
|
+
* is all that survives, and the pane renders it on reload. The real problem those rows caused was
|
|
24
|
+
* being replayed into the NEXT run's prompt as if they were answers, and that is fixed on the read
|
|
25
|
+
* side instead — see `assistant-transport.ts`'s `historyForTranscript`/`isAnsweredAssistantTurn`.
|
|
26
|
+
* Deleting them here would trade a history bug for a forensics hole.
|
|
27
|
+
*/
|
|
28
|
+
export declare function persistableMessages({ messages }: {
|
|
29
|
+
messages: ChatMessage[];
|
|
30
|
+
}, _options?: Record<string, never>): ChatMessage[];
|
|
31
|
+
/**
|
|
32
|
+
* The key the admin's chat writers use to remember "this message is already saved": the message id,
|
|
33
|
+
* plus the run id when there is one.
|
|
34
|
+
*
|
|
35
|
+
* The run id is part of the key because `@jini-ai/chat`'s `retry` keeps the assistant message id and
|
|
36
|
+
* starts a NEW run under it. Keyed by id alone, the first run had already marked the id saved, so the
|
|
37
|
+
* retry's in-flight stub was never written (and the API finalizer never followed it) and its final
|
|
38
|
+
* answer was never saved either. A message with no run id (a user turn, an old row) keys by id alone.
|
|
39
|
+
*/
|
|
40
|
+
export declare function messageWriteKey({ message }: {
|
|
41
|
+
message: Pick<ChatMessage, "id" | "runId">;
|
|
42
|
+
}, _options?: Record<string, never>): string;
|
|
43
|
+
/**
|
|
44
|
+
* The one message worth a durable "this run is in flight" stub write — the newest message, if it is
|
|
45
|
+
* a non-terminal assistant turn that has acquired a `runId`. `null` when there is nothing to record.
|
|
46
|
+
*
|
|
47
|
+
* Exists for reattach (2026-09-11 investigation): {@link persistableMessages} above deliberately
|
|
48
|
+
* never durably writes a `queued`/`running` assistant row (this file's own module doc explains why —
|
|
49
|
+
* avoiding a write per streamed token), which means today a run's `runId` exists NOWHERE outside the
|
|
50
|
+
* live browser tab that started it. Reload the tab, switch conversations and back, or lose the tab
|
|
51
|
+
* to a crash mid-run, and the id needed to resume watching that run is gone even though the run
|
|
52
|
+
* itself is still going server-side — there is nothing left for a reattach to find. `useAssistantChats`'s
|
|
53
|
+
* `persistRunStub` calls this once per `onMessagesChange` delta and writes at most once per message
|
|
54
|
+
* id (see that function's own doc), so this adds exactly one extra `PUT` per run, not one per token.
|
|
55
|
+
*
|
|
56
|
+
* Scoped to the LAST message only, not "any non-terminal assistant message anywhere in the array":
|
|
57
|
+
* `useConversation.ts`'s `sendMessage`/`retry` always append the run's assistant placeholder last,
|
|
58
|
+
* so a non-terminal row earlier in the array can only be a stale leftover from an earlier defect or
|
|
59
|
+
* race — recording ITS id as "the current run" would point a future reattach at the wrong turn.
|
|
60
|
+
*
|
|
61
|
+
* `saveMessage`'s server-side route upserts by message id (`ON CONFLICT(id) DO UPDATE`,
|
|
62
|
+
* `Jini/packages/sqlite`'s `chat-history/store.ts`), so this stub write and the message's eventual
|
|
63
|
+
* terminal-state write (through the normal {@link persistableMessages} path) converge on the same
|
|
64
|
+
* row rather than creating two — the terminal write simply overwrites `run_status`/`content`/
|
|
65
|
+
* `events_json` in place once the run settles.
|
|
66
|
+
*
|
|
67
|
+
* @complexity O(1) — reads only the last array element, unlike {@link persistableMessages}'s O(n)
|
|
68
|
+
* scan, so calling this on every delta alongside the existing `flush` call costs nothing material.
|
|
69
|
+
*/
|
|
70
|
+
export declare function activeRunStub({ messages }: {
|
|
71
|
+
messages: ChatMessage[];
|
|
72
|
+
}, _options?: Record<string, never>): ChatMessage | null;
|
|
73
|
+
//# sourceMappingURL=messages.d.ts.map
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { isTerminalRunStatus } from "../messages.js";
|
|
2
|
+
/**
|
|
3
|
+
* The subset of a live transcript that is worth writing: user turns, and assistant turns whose run
|
|
4
|
+
* has reached a terminal status.
|
|
5
|
+
*
|
|
6
|
+
* An assistant message that is still `queued` or `running` is rewritten on every delta, so
|
|
7
|
+
* persisting it would turn one reply into hundreds of writes for a row that is about to be
|
|
8
|
+
* replaced anyway.
|
|
9
|
+
*
|
|
10
|
+
* A `failed`/`canceled` assistant turn with EMPTY content is kept deliberately, and this is the
|
|
11
|
+
* place a future "stop writing those, they are just noise" change would land — don't (2026-09-11
|
|
12
|
+
* chat-lifecycle repair, Defect 3). The row's `events_json` is the only durable record of why the
|
|
13
|
+
* run died: the daemon's own event log is in-memory and gone with the process, so the stored
|
|
14
|
+
* `"Run failed — the agent process exited without answering"` notice with its exit code and signal
|
|
15
|
+
* is all that survives, and the pane renders it on reload. The real problem those rows caused was
|
|
16
|
+
* being replayed into the NEXT run's prompt as if they were answers, and that is fixed on the read
|
|
17
|
+
* side instead — see `assistant-transport.ts`'s `historyForTranscript`/`isAnsweredAssistantTurn`.
|
|
18
|
+
* Deleting them here would trade a history bug for a forensics hole.
|
|
19
|
+
*/
|
|
20
|
+
export function persistableMessages({ messages }, _options = {}) {
|
|
21
|
+
return messages.filter((message) => message.role === "user" || isTerminalRunStatus({ status: message.runStatus }));
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* The key the admin's chat writers use to remember "this message is already saved": the message id,
|
|
25
|
+
* plus the run id when there is one.
|
|
26
|
+
*
|
|
27
|
+
* The run id is part of the key because `@jini-ai/chat`'s `retry` keeps the assistant message id and
|
|
28
|
+
* starts a NEW run under it. Keyed by id alone, the first run had already marked the id saved, so the
|
|
29
|
+
* retry's in-flight stub was never written (and the API finalizer never followed it) and its final
|
|
30
|
+
* answer was never saved either. A message with no run id (a user turn, an old row) keys by id alone.
|
|
31
|
+
*/
|
|
32
|
+
export function messageWriteKey({ message }, _options = {}) {
|
|
33
|
+
return message.runId === undefined ? message.id : `${message.id}:${message.runId}`;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* The one message worth a durable "this run is in flight" stub write — the newest message, if it is
|
|
37
|
+
* a non-terminal assistant turn that has acquired a `runId`. `null` when there is nothing to record.
|
|
38
|
+
*
|
|
39
|
+
* Exists for reattach (2026-09-11 investigation): {@link persistableMessages} above deliberately
|
|
40
|
+
* never durably writes a `queued`/`running` assistant row (this file's own module doc explains why —
|
|
41
|
+
* avoiding a write per streamed token), which means today a run's `runId` exists NOWHERE outside the
|
|
42
|
+
* live browser tab that started it. Reload the tab, switch conversations and back, or lose the tab
|
|
43
|
+
* to a crash mid-run, and the id needed to resume watching that run is gone even though the run
|
|
44
|
+
* itself is still going server-side — there is nothing left for a reattach to find. `useAssistantChats`'s
|
|
45
|
+
* `persistRunStub` calls this once per `onMessagesChange` delta and writes at most once per message
|
|
46
|
+
* id (see that function's own doc), so this adds exactly one extra `PUT` per run, not one per token.
|
|
47
|
+
*
|
|
48
|
+
* Scoped to the LAST message only, not "any non-terminal assistant message anywhere in the array":
|
|
49
|
+
* `useConversation.ts`'s `sendMessage`/`retry` always append the run's assistant placeholder last,
|
|
50
|
+
* so a non-terminal row earlier in the array can only be a stale leftover from an earlier defect or
|
|
51
|
+
* race — recording ITS id as "the current run" would point a future reattach at the wrong turn.
|
|
52
|
+
*
|
|
53
|
+
* `saveMessage`'s server-side route upserts by message id (`ON CONFLICT(id) DO UPDATE`,
|
|
54
|
+
* `Jini/packages/sqlite`'s `chat-history/store.ts`), so this stub write and the message's eventual
|
|
55
|
+
* terminal-state write (through the normal {@link persistableMessages} path) converge on the same
|
|
56
|
+
* row rather than creating two — the terminal write simply overwrites `run_status`/`content`/
|
|
57
|
+
* `events_json` in place once the run settles.
|
|
58
|
+
*
|
|
59
|
+
* @complexity O(1) — reads only the last array element, unlike {@link persistableMessages}'s O(n)
|
|
60
|
+
* scan, so calling this on every delta alongside the existing `flush` call costs nothing material.
|
|
61
|
+
*/
|
|
62
|
+
export function activeRunStub({ messages }, _options = {}) {
|
|
63
|
+
const last = messages.at(-1);
|
|
64
|
+
if (!last || last.role !== "assistant" || last.runId === undefined)
|
|
65
|
+
return null;
|
|
66
|
+
return isTerminalRunStatus({ status: last.runStatus }) ? null : last;
|
|
67
|
+
}
|
|
68
|
+
//# sourceMappingURL=messages.js.map
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import type { AgentEvent } from "../events.js";
|
|
2
|
+
import type { ChatMessage } from "../messages.js";
|
|
3
|
+
import type { ChatTransport, RunHandlers, StartRunInput } from "../transport.js";
|
|
4
|
+
import type { ExecutionConfig } from "@jini-ai/ui";
|
|
5
|
+
import type { RunNotices } from "../run-events/entry.js";
|
|
6
|
+
import type { createAgUiRunClient } from "./ag-ui-transport.js";
|
|
7
|
+
import type { DurableRunBinding } from "./durable-run-subscription.js";
|
|
8
|
+
export interface AssistantTransportOptions {
|
|
9
|
+
/**
|
|
10
|
+
* Read fresh on every `startRun` call, never captured once — matching `AssistantDock.tsx`'s own
|
|
11
|
+
* `runContext` convention (see that file's doc on why `frontendBindToken` is read the same way):
|
|
12
|
+
* the operator can flip the runtime picker's mode mid-session, and a captured value would keep
|
|
13
|
+
* routing every later turn through whichever mode was selected when the transport was first
|
|
14
|
+
* built (`AssistantDock.tsx` memoizes the transport once, for the reason its own comment gives —
|
|
15
|
+
* rebuilding it would drop in-flight runs).
|
|
16
|
+
*/
|
|
17
|
+
getExecutionConfig?: (required: Record<string, never>, optional: Record<string, never>) => ExecutionConfig;
|
|
18
|
+
/**
|
|
19
|
+
* ADR-059's AG-UI canary toggle — deliberately NOT a value on {@link ExecutionConfig}: that type
|
|
20
|
+
* is `@jini-ai/ui`'s own closed `'local-cli' | 'byok'` union, a separate repo/package, and
|
|
21
|
+
* extending it would mean a cross-repo edit this ADR explicitly avoids (Decision 1: "a zero-touch
|
|
22
|
+
* addition to Jini"). Read fresh on every `startRun` call, same "never captured" convention
|
|
23
|
+
* {@link getExecutionConfig} already establishes, for the same reason: an operator can flip this
|
|
24
|
+
* mid-session without rebuilding the memoized transport.
|
|
25
|
+
*/
|
|
26
|
+
getAgUiEnabled?: (required: Record<string, never>, optional: Record<string, never>) => boolean;
|
|
27
|
+
/**
|
|
28
|
+
* Agent ids whose own CLI/ACP session already carries multi-turn conversation memory across
|
|
29
|
+
* spawns — `@jini-ai/agent-runtime`'s `resumesSessionViaCli`/`resumesSessionViaAcpLoad`, projected
|
|
30
|
+
* client-safe as `AssistantAgentSummary.carriesOwnMemory` (`assistant/agents.ts` in the host server).
|
|
31
|
+
* {@link runPrompt}'s own doc has the full "used to send only the newest message" background; this
|
|
32
|
+
* option is the inverse regression it introduced for exactly these agents — see `startRun`'s Local
|
|
33
|
+
* CLI branch below, which resends the doc's own contract instead of the full transcript ONLY for an
|
|
34
|
+
* agentId this set contains: "the caller trusts this adapter's CLI to carry its own multi-turn
|
|
35
|
+
* conversation memory... and should skip resending the rendered transcript on follow-up turns"
|
|
36
|
+
* (`types.ts`). Resending it anyway would duplicate everything the daemon's own `--resume`/
|
|
37
|
+
* `session/load` already restores (`agent-session-resume.ts`).
|
|
38
|
+
*
|
|
39
|
+
* Read fresh on every `startRun` call, same "never captured" convention {@link getExecutionConfig}/
|
|
40
|
+
* {@link getAgUiEnabled} already establish: the admin's own `/api/agents` probe resolves
|
|
41
|
+
* asynchronously and can still be in flight when this transport is first built, and an operator can
|
|
42
|
+
* switch the Local CLI agent pick mid-session.
|
|
43
|
+
*
|
|
44
|
+
* Omitted, or an agentId absent from the returned set, keeps today's behavior — the full transcript
|
|
45
|
+
* is sent. That is the SAFE default for a def this option's source has not resolved as resume-capable
|
|
46
|
+
* yet: sending redundant history to a resume-capable def costs extra tokens, but withholding history
|
|
47
|
+
* from a stateless def would silently erase its memory — the two failure directions are not
|
|
48
|
+
* symmetric, so "unknown" must resolve to "send everything," not to "send only the latest message."
|
|
49
|
+
*/
|
|
50
|
+
getResumeCapableAgentIds?: (required: Record<string, never>, optional: Record<string, never>) => ReadonlySet<string>;
|
|
51
|
+
/**
|
|
52
|
+
* This pane's conversation id, adopting one if none is active yet —
|
|
53
|
+
* `useAssistantChats.ensureConversationId` (`hooks/use-assistant-chats.hooks.ts`), wired through
|
|
54
|
+
* `AssistantDock`'s `useAssistantTransport`. Awaited by `startRun` ONLY when `input.context` names
|
|
55
|
+
* no conversation, which in practice means the first turn of a chat.
|
|
56
|
+
*
|
|
57
|
+
* Why the transport has to be the one to ask: the admin adopts a conversation lazily, from the
|
|
58
|
+
* first message delta — and `@jini-ai/chat`'s `useChatPane.sendPrompt` freezes `runContext(...)`
|
|
59
|
+
* into `input.context` BEFORE calling `conversation.sendMessage(...)`, the very call that produces
|
|
60
|
+
* that delta. So on turn 1 there is no id to capture yet, and `runContext` (synchronous by
|
|
61
|
+
* contract) has no way to wait for one. `startRun` is the last point in the chain that can still
|
|
62
|
+
* `await`, which makes it the only place the run can acquire the identity its agent-CLI session id
|
|
63
|
+
* gets filed under. Without it, `agent-daemon-server.ts`'s `onStarted` skipped its entire
|
|
64
|
+
* session-capture subscription for turn 1, and turn 2 — sent only the bare latest user message,
|
|
65
|
+
* trusting a resume that had nothing stored — answered with none of the conversation.
|
|
66
|
+
*
|
|
67
|
+
* Contract: resolves `null` rather than rejecting when creation fails. `startRun` treats that as
|
|
68
|
+
* "send the turn anyway, with no conversationId" — one run with no resumable session is a far
|
|
69
|
+
* smaller loss than a run that never happens.
|
|
70
|
+
*/
|
|
71
|
+
ensureConversationId?: (required: Record<string, never>, optional: Record<string, never>) => Promise<string | null>;
|
|
72
|
+
/**
|
|
73
|
+
* Writes one user message to durable storage — `useAssistantChats.persistUserTurn`
|
|
74
|
+
* (`hooks/use-assistant-chats.hooks.ts`), wired through `AssistantDock`'s `useAssistantTransport`.
|
|
75
|
+
* Awaited by `startRun` on the Local CLI path before `POST /api/runs`, including a delta's pending
|
|
76
|
+
* PUT. Acceptance persists the same message with its stub if the browser write fails. See
|
|
77
|
+
* {@link persistUserTurnBeforeDispatch} for the defect and the idempotency contract.
|
|
78
|
+
*
|
|
79
|
+
* Optional for transports without the dock's persistence hook; acceptance still receives the
|
|
80
|
+
* exact user message, and the delta-driven `flush` can write the same ID afterward.
|
|
81
|
+
* Its rejection is swallowed at the call site rather than failing the turn.
|
|
82
|
+
*/
|
|
83
|
+
persistUserTurn?: (required: {
|
|
84
|
+
conversationId: string;
|
|
85
|
+
message: ChatMessage;
|
|
86
|
+
}, optional: Record<string, never>) => Promise<void>;
|
|
87
|
+
}
|
|
88
|
+
export interface AssistantTransportPorts {
|
|
89
|
+
readonly runsUrl: string;
|
|
90
|
+
readonly byokTurnUrl: string;
|
|
91
|
+
readonly byokRunIdPrefix: string;
|
|
92
|
+
readonly notices: RunNotices;
|
|
93
|
+
readonly fetch: typeof fetch;
|
|
94
|
+
readonly mintId: (required: Record<string, never>, optional: Record<string, never>) => string;
|
|
95
|
+
readonly projectPrompt: (required: {
|
|
96
|
+
prompt: string;
|
|
97
|
+
context: StartRunInput["context"];
|
|
98
|
+
}, optional: Record<string, never>) => string;
|
|
99
|
+
readonly projectInput: (required: {
|
|
100
|
+
input: StartRunInput;
|
|
101
|
+
}, optional: Record<string, never>) => StartRunInput;
|
|
102
|
+
readonly bindRun: (required: {
|
|
103
|
+
runId: string;
|
|
104
|
+
binding: DurableRunBinding;
|
|
105
|
+
}, optional: Record<string, never>) => void;
|
|
106
|
+
readonly runBinding: (required: {
|
|
107
|
+
runId: string;
|
|
108
|
+
}, optional: Record<string, never>) => DurableRunBinding | undefined;
|
|
109
|
+
readonly followRun: (required: {
|
|
110
|
+
runId: string;
|
|
111
|
+
handlers: RunHandlers;
|
|
112
|
+
signal?: AbortSignal;
|
|
113
|
+
}, optional: Record<string, never>) => void;
|
|
114
|
+
readonly acceptRun: (required: {
|
|
115
|
+
body: unknown;
|
|
116
|
+
signal?: AbortSignal;
|
|
117
|
+
requestSignal?: AbortSignal;
|
|
118
|
+
messageId: unknown;
|
|
119
|
+
conversationId?: string;
|
|
120
|
+
}, optional: Record<string, never>) => Promise<Response>;
|
|
121
|
+
readonly agUi: ReturnType<typeof createAgUiRunClient>;
|
|
122
|
+
}
|
|
123
|
+
export interface ByokFrameContext {
|
|
124
|
+
collected: AgentEvent[];
|
|
125
|
+
handlers: RunHandlers;
|
|
126
|
+
finish: () => void;
|
|
127
|
+
}
|
|
128
|
+
export interface ByokStreamContext extends ByokFrameContext {
|
|
129
|
+
runId: string;
|
|
130
|
+
controller: AbortController;
|
|
131
|
+
}
|
|
132
|
+
export interface AssistantTransportClient {
|
|
133
|
+
createAssistantTransport(required: {
|
|
134
|
+
options?: AssistantTransportOptions;
|
|
135
|
+
}, optional?: Record<string, never>): ChatTransport;
|
|
136
|
+
historyForTranscript(required: {
|
|
137
|
+
history: readonly ChatMessage[];
|
|
138
|
+
}, optional?: Record<string, never>): ChatMessage[];
|
|
139
|
+
undeliveredUserPrompt(required: {
|
|
140
|
+
history: readonly ChatMessage[];
|
|
141
|
+
}, optional?: Record<string, never>): string;
|
|
142
|
+
buildLocalCliContextRef(required: {
|
|
143
|
+
input: StartRunInput;
|
|
144
|
+
prompt: string;
|
|
145
|
+
resolvedConversationId?: string;
|
|
146
|
+
}, optional?: Record<string, never>): Record<string, unknown>;
|
|
147
|
+
handleByokFrame(required: {
|
|
148
|
+
frame: {
|
|
149
|
+
event: string;
|
|
150
|
+
data: string;
|
|
151
|
+
};
|
|
152
|
+
ctx: ByokFrameContext;
|
|
153
|
+
}, optional?: Record<string, never>): void;
|
|
154
|
+
consumeByokStream(required: {
|
|
155
|
+
body: ReadableStream<Uint8Array>;
|
|
156
|
+
ctx: ByokStreamContext;
|
|
157
|
+
}, optional?: Record<string, never>): Promise<void>;
|
|
158
|
+
}
|
|
159
|
+
/** Bind transport effects once; in-flight controllers belong to this client, never the package.
|
|
160
|
+
* Positional run methods implement the existing ChatTransport contract. */
|
|
161
|
+
export declare function createAssistantTransportClient({ ports }: {
|
|
162
|
+
ports: AssistantTransportPorts;
|
|
163
|
+
}, _options?: Record<string, never>): AssistantTransportClient;
|
|
164
|
+
//# sourceMappingURL=transport.d.ts.map
|