@jini-ai/chat 0.6.15-rc.2 → 0.6.16
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 +6 -1
- package/dist/core/accepted-input.js +20 -11
- package/dist/core/ag-ui/entry.js +3 -1
- package/dist/core/agent-plugin-chat-state.js +3 -1
- package/dist/core/antigravity/tool-presentation.d.ts +13 -0
- package/dist/core/antigravity/tool-presentation.js +20 -0
- package/dist/core/assistant-client/ag-ui-transport.js +14 -2
- package/dist/core/assistant-client/attachment-liveness.js +1 -1
- package/dist/core/assistant-client/durable-run-subscription.js +2 -1
- package/dist/core/assistant-client/transport.js +13 -7
- package/dist/core/assistant-content.js +2 -1
- package/dist/core/composer-history.js +2 -1
- package/dist/core/durable-projection.js +2 -1
- package/dist/core/durable-runs/continuation.js +2 -1
- package/dist/core/events.d.ts +5 -1
- package/dist/core/messages.d.ts +5 -1
- package/dist/core/partial-json.js +18 -9
- package/dist/core/persistence/title.d.ts +1 -1
- package/dist/core/persistence/title.js +25 -7
- package/dist/core/question-form/normalize.js +1 -1
- package/dist/core/question-form/scan.js +25 -15
- package/dist/core/question-form/secret-answers.js +2 -1
- package/dist/core/run-activity.js +8 -4
- package/dist/core/run-events/entry.js +6 -2
- package/dist/core/tool-events.d.ts +18 -8
- package/dist/core/tool-events.js +72 -16
- package/dist/core/tool-presentation.d.ts +14 -0
- package/dist/core/tool-presentation.js +8 -0
- package/dist/core/transcript.js +13 -6
- package/dist/core/turn-usage.js +7 -2
- package/dist/core/user-text-redaction.d.ts +4 -6
- package/dist/core/user-text-redaction.js +13 -6
- package/dist/core/util/manifest.js +13 -11
- package/dist/core/util/markdown-context.d.ts +14 -0
- package/dist/core/util/markdown-context.js +42 -4
- package/dist/core/util/markup-attributes.js +1 -1
- package/dist/core/util/parser.js +7 -7
- package/dist/core/util/pointer.js +22 -6
- package/dist/core/util/recover.js +5 -8
- package/dist/core/util/strip.js +12 -15
- package/dist/core/util/validate.js +30 -18
- package/dist/embed-widget/browser/frontend-session-bridge.d.ts +99 -0
- package/dist/embed-widget/browser/frontend-session-bridge.js +384 -0
- package/dist/embed-widget/browser/lazy-loader.js +16 -12
- package/dist/embed-widget/browser/session-store.d.ts +3 -9
- package/dist/embed-widget/browser/session-store.js +6 -38
- package/dist/embed-widget/react/dialog.hooks.js +2 -2
- package/dist/embed-widget/react/embed.d.ts +3 -10
- package/dist/embed-widget/react/embed.js +8 -52
- package/dist/embed-widget/react/header.hooks.js +1 -1
- package/dist/embed-widget/transports/fetch-sse.js +2 -1
- package/dist/embed-widget/vanilla/index.d.ts +0 -5
- package/dist/embed-widget/vanilla/index.js +23 -26
- package/dist/embed-widget/vanilla/state.d.ts +3 -11
- package/dist/embed-widget/vanilla/state.js +10 -39
- package/dist/embed-widget/vanilla/style.css +1 -1
- package/dist/react/agent-bridge/frontend-session-bridge.d.ts +2 -56
- package/dist/react/agent-bridge/frontend-session-bridge.js +2 -248
- package/dist/react/components/AttachmentPreviewModal.hooks.js +2 -1
- package/dist/react/components/ChatSettingsDialog.js +18 -9
- package/dist/react/components/Composer.js +5 -4
- package/dist/react/components/ConversationList.js +5 -4
- package/dist/react/components/Icon.d.ts +1 -1
- package/dist/react/components/Icon.js +1 -0
- package/dist/react/components/Markdown.d.ts +1 -0
- package/dist/react/components/Markdown.js +30 -27
- package/dist/react/components/McpUiSurfaceCard.js +2 -1
- package/dist/react/components/MessageList.d.ts +1 -1
- package/dist/react/components/MessageList.hooks.js +3 -2
- package/dist/react/components/MessageRow.d.ts +3 -0
- package/dist/react/components/MessageRow.js +32 -14
- package/dist/react/components/MessageRow.usage.js +3 -1
- package/dist/react/components/QuestionForm.js +4 -5
- package/dist/react/components/SentAttachment.js +4 -1
- package/dist/react/components/TodoCard.js +9 -1
- package/dist/react/components/ToolCard.d.ts +3 -1
- package/dist/react/components/ToolCard.js +84 -64
- package/dist/react/components/ToolCard.rules.d.ts +20 -0
- package/dist/react/components/ToolCard.rules.js +92 -0
- package/dist/react/components/composer-discovery.js +8 -8
- package/dist/react/features/chat-pane/components/AgentPluginUsage.js +1 -2
- package/dist/react/features/chat-pane/components/AgentRuntimePicker.d.ts +1 -1
- package/dist/react/features/chat-pane/components/AgentRuntimePicker.js +31 -9
- package/dist/react/features/chat-pane/components/ChatPane.js +7 -5
- package/dist/react/features/chat-pane/components/RuntimeDropUp.js +1 -1
- package/dist/react/features/chat-pane/create-daemon-attachment-uploader.js +1 -1
- package/dist/react/features/chat-pane/hooks/useAgentRuntimePicker.hooks.js +2 -2
- package/dist/react/features/chat-pane/hooks/useChatPane.hooks.d.ts +3 -1
- package/dist/react/features/chat-pane/hooks/useChatPane.hooks.js +13 -5
- package/dist/react/features/chat-pane/hooks/useChatPaneAgentControl.hooks.js +2 -2
- package/dist/react/features/chat-pane/hooks/useChatPaneControlsHeight.hooks.js +1 -1
- package/dist/react/features/chat-pane/hooks/useChatPaneRuntimeInventory.hooks.js +2 -2
- package/dist/react/features/chat-pane/hooks/useChatPaneWorkingDirectory.hooks.js +2 -2
- package/dist/react/features/chat-pane/hooks/useRuntimeDropUp.hooks.js +2 -2
- package/dist/react/features/chat-pane/model-options.js +4 -2
- package/dist/react/features/chat-pane/styles.d.ts +1 -1
- package/dist/react/features/chat-pane/styles.js +95 -5
- package/dist/react/features/chat-pane/types.d.ts +11 -0
- package/dist/react/hooks/assistant-chats.js +22 -8
- package/dist/react/hooks/useArtifactStream.js +2 -1
- package/dist/react/hooks/useComposer.js +2 -2
- package/dist/react/hooks/useComposerHistoryKeys.hooks.js +2 -2
- package/dist/react/hooks/useConversation.js +9 -4
- package/dist/react/hooks/useExtEventGroups.js +4 -2
- package/dist/react/hooks/useLatestOperation.js +2 -1
- package/dist/react/hooks/useQuestionForms.js +32 -22
- package/dist/react/hooks/useRunStream.js +4 -2
- package/dist/react/hooks/useUserTextGuard.d.ts +1 -0
- package/dist/react/hooks/useUserTextGuard.js +3 -1
- package/dist/react/message-blocks.js +4 -2
- package/dist/react/styles/reference.css +9 -0
- package/dist/react/tool-call-display.d.ts +11 -0
- package/dist/react/tool-call-display.js +59 -2
- package/dist/server/durable-runs/recover.js +13 -5
- package/dist/server/run-finalizer/entry.js +1 -0
- package/dist/store/runtime/chat-history-store.js +9 -3
- package/dist/store/runtime/durable-run-store.js +34 -24
- package/dist/store/runtime/run-ledger.js +7 -4
- package/dist/store/sql/boundary.js +3 -1
- package/dist/store/sql/cursor.js +8 -3
- package/dist/store/sql/store.js +36 -28
- package/package.json +93 -88
- package/dist/core/browser/page-actions.d.ts +0 -121
- package/dist/core/browser/page-actions.js +0 -115
- package/dist/embed-widget/browser/page-actions.d.ts +0 -2
- package/dist/embed-widget/browser/page-actions.js +0 -3
package/README.md
CHANGED
|
@@ -197,12 +197,17 @@ required argument objects: e.g. `isTerminalRunStatus({ status })`,
|
|
|
197
197
|
`registerExtEventRenderer({ name, renderer }, { slotKey })`. A slot-key callback receives `{ data }`.
|
|
198
198
|
`interleaveMessageBlocks({ events, content, rows }, { slotOf })` retains its reconstruction checks.
|
|
199
199
|
The isolated `core/run-events`, `core/ag-ui`, `server/run-finalizer`, `react/embed`,
|
|
200
|
-
`transports/fetch-sse`, `browser/session-store` and `browser/
|
|
200
|
+
`transports/fetch-sse`, `browser/session-store` and `browser/frontend-session-bridge` subpaths are published. Page control uses the shared frontend bridge and page executor; the retired directive executor and cross-page action queue have been removed.
|
|
201
201
|
`FrontendSessionBridgeArgs` is exported from `react`; hosts pass baseUrl and openStream, optionally request or native fetch.
|
|
202
202
|
The SSE transport takes an injected decoder; browser hosts must supply a browser-safe module.
|
|
203
203
|
|
|
204
204
|
## React integration boundaries
|
|
205
205
|
|
|
206
|
+
`ChatPane` accepts an optional `sendingLabel?: string` for host-localized startup feedback,
|
|
207
|
+
for example `<ChatPane transport={transport} sendingLabel="Connecting to the assistant…" />`.
|
|
208
|
+
The active, sending activity line displays it verbatim until run acceptance. Omitting the prop
|
|
209
|
+
restores the translated `Sending…` label; it has no effect once sending ends.
|
|
210
|
+
|
|
206
211
|
Model/provider selection belongs to the host-injected `ModelAgentPickerSlot`, because choosing
|
|
207
212
|
an LLM or agent is a provider concern rather than a chat-domain concern. The unused internal
|
|
208
213
|
picker was removed after a fresh caller check confirmed that only its own tests reached it.
|
|
@@ -15,7 +15,9 @@ export function projectAcceptedInputMessages({ messages }, _options = {}) {
|
|
|
15
15
|
});
|
|
16
16
|
if (inputs.length === 0)
|
|
17
17
|
return [message];
|
|
18
|
-
const
|
|
18
|
+
const turn = { ...message };
|
|
19
|
+
delete turn.attachments;
|
|
20
|
+
delete turn.secretRedaction;
|
|
19
21
|
// MOVE: Older receipts have no per-input files; retain those legacy files on the final bubble.
|
|
20
22
|
return inputs.map((input, index) => ({ ...turn, id: input.id, content: input.text,
|
|
21
23
|
attachments: input.attachments ?? (index === inputs.length - 1 ? message.attachments ?? [] : []), ...(input.secretRedaction ? { secretRedaction: input.secretRedaction } : {}) }));
|
|
@@ -86,7 +88,8 @@ function expandQuestionAnswers({ events }, { answerContexts } = {}) {
|
|
|
86
88
|
const questions = new Map(events.flatMap(event => event.kind === 'tool_use' ? [[event.id, event.input]] : []));
|
|
87
89
|
const answers = new Map(events.flatMap(event => {
|
|
88
90
|
const context = event.kind === 'tool_result' ? answerContexts?.get(event.toolUseId) : undefined;
|
|
89
|
-
const
|
|
91
|
+
const originalInput = event.kind === 'tool_result' ? questions.get(event.toolUseId) : undefined;
|
|
92
|
+
const input = context ? context.input : originalInput;
|
|
90
93
|
const answer = questionAnswerEvent({ event, input }, { includeTitle: context?.includeTitle === true });
|
|
91
94
|
return answer ? [[answer.data.toolUseId, answer]] : [];
|
|
92
95
|
}));
|
|
@@ -121,13 +124,17 @@ function matchOptimisticInput({ input, optimistic, seen }, _options = {}) {
|
|
|
121
124
|
const next = candidates?.events[candidates.index];
|
|
122
125
|
const follows = next?.data.afterInputId === undefined || seen.has(next.data.afterInputId);
|
|
123
126
|
// Keep predecessor testing before the caller adds this receipt's ids to seen.
|
|
124
|
-
|
|
127
|
+
if (input.data.toolUseId)
|
|
128
|
+
return optimistic.byQuestion.get(input.data.toolUseId);
|
|
129
|
+
return follows ? next : undefined;
|
|
125
130
|
}
|
|
126
131
|
/** Apply authoritative receipt data while retaining local identity and queued constituents. */
|
|
127
132
|
function mergeAcceptedReceipt({ input, pending }, _options = {}) {
|
|
128
133
|
// Replayed queue acceptance cannot undo confirmed consumption.
|
|
129
134
|
const deliveryState = pending.data.deliveryState === 'consumed' ? 'consumed' : input.data.deliveryState ?? pending.data.deliveryState;
|
|
130
|
-
const
|
|
135
|
+
const pendingData = { ...pending.data };
|
|
136
|
+
delete pendingData.optimistic;
|
|
137
|
+
delete pendingData.afterInputId;
|
|
131
138
|
const aliases = [...new Set([...(input.data.aliases ?? []), input.data.id, ...(pending.data.aliases ?? [])])].filter(id => id !== pending.data.id);
|
|
132
139
|
return { ...input, data: { ...pendingData, ...input.data, id: pending.data.id,
|
|
133
140
|
// Legacy acceptance receipts omit state; HTTP acceptance still means queued until consumption.
|
|
@@ -199,8 +206,10 @@ export function projectAcceptedInputEvents({ events }, options) {
|
|
|
199
206
|
return projectEvents({ events }, options);
|
|
200
207
|
// Event arrays are replaced, never mutated, so a transcript update re-projects only changed messages.
|
|
201
208
|
let projected = defaultProjections.get(events);
|
|
202
|
-
if (!projected)
|
|
203
|
-
|
|
209
|
+
if (!projected) {
|
|
210
|
+
projected = projectEvents({ events }, {});
|
|
211
|
+
defaultProjections.set(events, projected);
|
|
212
|
+
}
|
|
204
213
|
return projected;
|
|
205
214
|
}
|
|
206
215
|
const defaultProjections = new WeakMap();
|
|
@@ -218,11 +227,11 @@ function projectEvents({ events }, options) {
|
|
|
218
227
|
continue;
|
|
219
228
|
const next = replacements.get(event) ?? event;
|
|
220
229
|
const input = acceptedInputEvent({ event: next });
|
|
221
|
-
if (input
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
230
|
+
if (!input || !emitted.has(input.data.id)) {
|
|
231
|
+
if (input)
|
|
232
|
+
emitted.add(input.data.id);
|
|
233
|
+
projected.push(next);
|
|
234
|
+
}
|
|
226
235
|
}
|
|
227
236
|
return projected;
|
|
228
237
|
}
|
package/dist/core/ag-ui/entry.js
CHANGED
|
@@ -6,7 +6,9 @@
|
|
|
6
6
|
* The host emits RUN_STARTED and terminal RUN_FINISHED/RUN_ERROR outside this pure projection. */
|
|
7
7
|
import { EventType } from '@ag-ui/core';
|
|
8
8
|
function asString(v) {
|
|
9
|
-
|
|
9
|
+
if (typeof v === "string")
|
|
10
|
+
return v;
|
|
11
|
+
return v == null ? "" : JSON.stringify(v);
|
|
10
12
|
}
|
|
11
13
|
function reduceUsagePayload(payload) {
|
|
12
14
|
const usage = (payload.usage ?? {});
|
|
@@ -13,7 +13,9 @@ function isStringList({ value }) {
|
|
|
13
13
|
}
|
|
14
14
|
/** A stored row is usable only when its exclusions and (null or listed) decision are all string ids. */
|
|
15
15
|
function usableChatState({ value }) {
|
|
16
|
-
|
|
16
|
+
if (!value || !isStringList({ value: value.excluded }))
|
|
17
|
+
return null;
|
|
18
|
+
return value.decision === null || isStringList({ value: value.decision?.selected }) ? value : null;
|
|
17
19
|
}
|
|
18
20
|
/** Corrupt/blocked storage permits a fresh question, as clearing localStorage does. */
|
|
19
21
|
export function readAgentPluginChatState({ conversationId }, { storage = browserStorage() } = {}) {
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/** Antigravity's saved CLI receipts use capitalized argument keys. Display only: never rewrite history. */
|
|
2
|
+
type Call = {
|
|
3
|
+
name: string;
|
|
4
|
+
input: unknown;
|
|
5
|
+
};
|
|
6
|
+
/** Translate agy's MCP envelope and file arguments into the generic chat presentation contract.
|
|
7
|
+
* Returns undefined for other shapes. Pure; O(k) when copying k file argument keys, otherwise O(1).
|
|
8
|
+
*/
|
|
9
|
+
export declare function antigravityToolPresentation({ name, input }: Call, _options?: Record<string, never>): (Call & {
|
|
10
|
+
wrapper?: Call;
|
|
11
|
+
}) | undefined;
|
|
12
|
+
export {};
|
|
13
|
+
//# sourceMappingURL=tool-presentation.d.ts.map
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/** A transport envelope must name both server and tool before its arguments can be unwrapped. */
|
|
2
|
+
function nonEmptyString(value) {
|
|
3
|
+
return typeof value === 'string' && value.length > 0;
|
|
4
|
+
}
|
|
5
|
+
/** Translate agy's MCP envelope and file arguments into the generic chat presentation contract.
|
|
6
|
+
* Returns undefined for other shapes. Pure; O(k) when copying k file argument keys, otherwise O(1).
|
|
7
|
+
*/
|
|
8
|
+
export function antigravityToolPresentation({ name, input }, _options = {}) {
|
|
9
|
+
if (input === null || typeof input !== 'object')
|
|
10
|
+
return undefined;
|
|
11
|
+
const args = input;
|
|
12
|
+
if (name === 'read_file' && typeof args.Path === 'string') {
|
|
13
|
+
return { name, input: { ...args, file_path: args.Path } };
|
|
14
|
+
}
|
|
15
|
+
if (name !== 'call_mcp_tool' || !nonEmptyString(args.ServerName) || !nonEmptyString(args.ToolName))
|
|
16
|
+
return undefined;
|
|
17
|
+
const call = { name: args.ToolName, input: args.Arguments };
|
|
18
|
+
return { ...call, wrapper: call };
|
|
19
|
+
}
|
|
20
|
+
//# sourceMappingURL=tool-presentation.js.map
|
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
import { EventType } from "@ag-ui/core";
|
|
2
2
|
import { createAgUiToAgentTranslationState, translateAgUiEventToAgentEvent } from "../ag-ui/entry.js";
|
|
3
|
+
/** Only the originating attachment can authorize page execution. Read its token for every turn;
|
|
4
|
+
* caller-supplied tool definitions never authorize or execute a transcript event. */
|
|
5
|
+
function frontendBinding({ input }, _options = {}) {
|
|
6
|
+
const token = input.context?.["frontendBindToken"];
|
|
7
|
+
return typeof token === "string" && token.length > 0 ? { frontendBindToken: token } : {};
|
|
8
|
+
}
|
|
3
9
|
/** Bind the official client without adding its dependency to every chat consumer. */
|
|
4
10
|
export function createAgUiRunClient({ ports }, _options = {}) {
|
|
5
11
|
/** In-flight AG-UI turns' driving `HttpAgent`, keyed by the client-minted runId — the ONLY way
|
|
@@ -54,8 +60,11 @@ export function createAgUiRunClient({ ports }, _options = {}) {
|
|
|
54
60
|
// none yet (e.g. the very first turn).
|
|
55
61
|
const threadId = input.conversationId ?? ports.mintId({}, {});
|
|
56
62
|
const agent = ports.createAgent({ url: ports.endpoint, threadId }, {});
|
|
63
|
+
if (input.signal.aborted)
|
|
64
|
+
throw new DOMException("Run cancelled", "AbortError");
|
|
57
65
|
agUiAgents.set(runId, agent);
|
|
58
|
-
|
|
66
|
+
const abort = () => agent.abortRun();
|
|
67
|
+
input.signal.addEventListener("abort", abort, { once: true });
|
|
59
68
|
const messages = input.history.filter((message) => message.content.trim().length > 0).map(toAgUiMessage);
|
|
60
69
|
const runAgentInput = {
|
|
61
70
|
threadId,
|
|
@@ -65,8 +74,9 @@ export function createAgUiRunClient({ ports }, _options = {}) {
|
|
|
65
74
|
tools: [],
|
|
66
75
|
context: [],
|
|
67
76
|
forwardedProps: {
|
|
77
|
+
...frontendBinding({ input }),
|
|
68
78
|
...(input.agentId ? { agentId: input.agentId } : {}),
|
|
69
|
-
...(input.history.
|
|
79
|
+
...(input.history.findLast(message => message.role === "user")?.secretRedaction?.secretRedacted ? { secretRedacted: true } : {}),
|
|
70
80
|
},
|
|
71
81
|
};
|
|
72
82
|
const collected = [];
|
|
@@ -76,6 +86,7 @@ export function createAgUiRunClient({ ports }, _options = {}) {
|
|
|
76
86
|
return;
|
|
77
87
|
settled = true;
|
|
78
88
|
agUiAgents.delete(runId);
|
|
89
|
+
input.signal.removeEventListener("abort", abort);
|
|
79
90
|
handlers.onDone(collected);
|
|
80
91
|
};
|
|
81
92
|
const state = createAgUiToAgentTranslationState({ customEventNames: ports.customEventNames }, {});
|
|
@@ -96,6 +107,7 @@ export function createAgUiRunClient({ ports }, _options = {}) {
|
|
|
96
107
|
},
|
|
97
108
|
error: (error) => {
|
|
98
109
|
agUiAgents.delete(runId);
|
|
110
|
+
input.signal.removeEventListener("abort", abort);
|
|
99
111
|
const err = error instanceof Error ? error : new Error(String(error));
|
|
100
112
|
if (!startSettled) {
|
|
101
113
|
startSettled = true;
|
|
@@ -47,7 +47,7 @@ async function isAttachmentStillServed(ref, options) {
|
|
|
47
47
|
* @complexity O(n) requests, at most `concurrency` concurrent; O(n) space for the verdicts.
|
|
48
48
|
*/
|
|
49
49
|
async function probeAll(refs, concurrency, probe) {
|
|
50
|
-
const verdicts =
|
|
50
|
+
const verdicts = Array.from({ length: refs.length }, () => false);
|
|
51
51
|
const entries = refs.entries();
|
|
52
52
|
async function worker() {
|
|
53
53
|
for (const [index, ref] of entries) {
|
|
@@ -29,7 +29,8 @@ export function followDurableRun({ runId: initialRunId, handlers, signal, bindin
|
|
|
29
29
|
handlers.onDone(message.events ?? []);
|
|
30
30
|
}
|
|
31
31
|
function open() {
|
|
32
|
-
|
|
32
|
+
streamEpoch += 1;
|
|
33
|
+
const epoch = streamEpoch;
|
|
33
34
|
dropped = false;
|
|
34
35
|
stopStream?.();
|
|
35
36
|
stopStream = ports.stream({ runId, cursor,
|
|
@@ -293,7 +293,9 @@ export function createAssistantTransportClient({ ports }, _options = {}) {
|
|
|
293
293
|
const payload = JSON.parse(frame.data);
|
|
294
294
|
ctx.handlers.onError(new Error(asString(payload.message) || "BYOK turn failed"));
|
|
295
295
|
}
|
|
296
|
-
else
|
|
296
|
+
else {
|
|
297
|
+
if (frame.event !== "end")
|
|
298
|
+
return;
|
|
297
299
|
const notice = terminalReasonNotice(readTerminalReason(frame.data, false));
|
|
298
300
|
if (notice) {
|
|
299
301
|
ctx.collected.push(notice);
|
|
@@ -391,7 +393,8 @@ export function createAssistantTransportClient({ ports }, _options = {}) {
|
|
|
391
393
|
// A response whose body stream errors out (e.g. a dropped connection) must not turn "the server
|
|
392
394
|
// rejected the request" into "the client crashed reading the rejection".
|
|
393
395
|
const detail = await response.text().catch(() => "");
|
|
394
|
-
|
|
396
|
+
const suffix = detail ? ": " + detail.slice(0, 300) : "";
|
|
397
|
+
return new Error(`${action} (${response.status})${suffix}`);
|
|
395
398
|
}
|
|
396
399
|
/**
|
|
397
400
|
* The BYOK run path (2026-08-04) — one held-open `POST` to `assistant-byok.ts`, no separate
|
|
@@ -573,8 +576,9 @@ export function createAssistantTransportClient({ ports }, _options = {}) {
|
|
|
573
576
|
prompt: ports.projectPrompt({ prompt, context: input.context }, {}),
|
|
574
577
|
// Select one complete transcript after binding; concatenating it with the tail repeats turns.
|
|
575
578
|
coldStartContext: ports.projectPrompt({ prompt: runPrompt(input.history), context: input.context }, {}),
|
|
576
|
-
// The transcript is already sanitized; retain
|
|
577
|
-
|
|
579
|
+
// The transcript is already sanitized; retain only the CURRENT user's signal across the
|
|
580
|
+
// daemon JSON boundary. Replaying an old signal reports another paste on every later turn.
|
|
581
|
+
...(newestUserMessage({ history: input.history }, {})?.secretRedaction?.secretRedacted ? { secretRedacted: true } : {}),
|
|
578
582
|
assistantMessageId: input.assistantMessageId,
|
|
579
583
|
...frontendBindTokenField(input),
|
|
580
584
|
...modelField(input),
|
|
@@ -766,7 +770,7 @@ export function createAssistantTransportClient({ ports }, _options = {}) {
|
|
|
766
770
|
// can never arrive.
|
|
767
771
|
if (runId.startsWith(BYOK_RUN_ID_PREFIX)) {
|
|
768
772
|
handlers.onDone([]);
|
|
769
|
-
return;
|
|
773
|
+
return undefined;
|
|
770
774
|
}
|
|
771
775
|
// Same "no server-side record to resume" reasoning as the BYOK branch above — see
|
|
772
776
|
// `assistant-transport-ag-ui.ts`'s own doc for why this path has no reattach story yet.
|
|
@@ -774,6 +778,7 @@ export function createAssistantTransportClient({ ports }, _options = {}) {
|
|
|
774
778
|
return ports.agUi.reattachAgUiRun({ handlers }, {});
|
|
775
779
|
}
|
|
776
780
|
subscribeToRun(runId, handlers, options?.signal);
|
|
781
|
+
return undefined;
|
|
777
782
|
},
|
|
778
783
|
async fetchRunStatus(runId) {
|
|
779
784
|
// Same reasoning as `reattachRun` above — no server-side run record exists for a BYOK run id,
|
|
@@ -794,7 +799,7 @@ export function createAssistantTransportClient({ ports }, _options = {}) {
|
|
|
794
799
|
// this exact id — there is no server-side run to `POST .../cancel` against.
|
|
795
800
|
if (runId.startsWith(BYOK_RUN_ID_PREFIX)) {
|
|
796
801
|
byokAbortControllers.get(runId)?.abort();
|
|
797
|
-
return;
|
|
802
|
+
return undefined;
|
|
798
803
|
}
|
|
799
804
|
if (ports.agUi.isAgUiRunId({ runId }, {})) {
|
|
800
805
|
return ports.agUi.stopAgUiRun({ runId }, {});
|
|
@@ -807,11 +812,12 @@ export function createAssistantTransportClient({ ports }, _options = {}) {
|
|
|
807
812
|
});
|
|
808
813
|
// 404: the daemon no longer knows this run (it restarted), so nothing is left running to stop.
|
|
809
814
|
if (response.status === 404)
|
|
810
|
-
return;
|
|
815
|
+
return undefined;
|
|
811
816
|
// `fetch` resolves on any status: without this a refused cancel (401/403/500) resolved exactly
|
|
812
817
|
// like a successful one while the run kept executing. `useRunStream` logs a rejection here.
|
|
813
818
|
if (!response.ok)
|
|
814
819
|
throw await refusalError({ action: "cancelling agent run failed", response }, {});
|
|
820
|
+
return undefined;
|
|
815
821
|
},
|
|
816
822
|
};
|
|
817
823
|
}
|
|
@@ -32,8 +32,9 @@ export function assistantContentFromEvents({ events }, _options = {}) {
|
|
|
32
32
|
for (const ev of dedupeToolUsesById({ events: events ? [...events] : undefined })) {
|
|
33
33
|
if (isAssistantStepBoundary({ event: ev })) {
|
|
34
34
|
stepBoundary = true;
|
|
35
|
+
continue;
|
|
35
36
|
}
|
|
36
|
-
|
|
37
|
+
if (ev.kind === 'text' && ev.text.length > 0) {
|
|
37
38
|
out += (stepBoundary ? separatorBefore({ out, text: ev.text }) : '') + ev.text;
|
|
38
39
|
stepBoundary = false;
|
|
39
40
|
}
|
|
@@ -21,7 +21,8 @@ export function createComposerHistoryState(_required, _optional = {}) {
|
|
|
21
21
|
return { entries: [], index: null, draft: null };
|
|
22
22
|
}
|
|
23
23
|
function hasModifierOrPopup({ input }, _optional = {}) {
|
|
24
|
-
|
|
24
|
+
const modified = input.shiftKey || input.ctrlKey || input.altKey || input.metaKey;
|
|
25
|
+
return Boolean(modified || input.isComposing || input.popupOpen);
|
|
25
26
|
}
|
|
26
27
|
/**
|
|
27
28
|
* Whether the selection sits on the first/last line. A selection spanning a newline is on
|
|
@@ -52,8 +52,9 @@ export function mergeRunEvents({ saved, incoming }, _optional = {}) {
|
|
|
52
52
|
skip = Math.max(0, skip - event.text.length);
|
|
53
53
|
if (text)
|
|
54
54
|
result.push({ ...event, text });
|
|
55
|
+
continue;
|
|
55
56
|
}
|
|
56
|
-
|
|
57
|
+
if (claimNewKey({ key: identity(event, incomingUsage), keys })) {
|
|
57
58
|
result.push(event);
|
|
58
59
|
}
|
|
59
60
|
}
|
|
@@ -6,7 +6,8 @@ export function canonicalToolInput({ value }, _optional = {}) {
|
|
|
6
6
|
return `[${value.map((item) => canonicalToolInput({ value: item }, {})).join(",")}]`;
|
|
7
7
|
if (value !== null && typeof value === "object") {
|
|
8
8
|
const object = value;
|
|
9
|
-
|
|
9
|
+
const fields = Object.keys(object).sort().map((key) => `${JSON.stringify(key)}:${canonicalToolInput({ value: object[key] }, {})}`);
|
|
10
|
+
return `{${fields.join(",")}}`;
|
|
10
11
|
}
|
|
11
12
|
return JSON.stringify(value) ?? "null";
|
|
12
13
|
}
|
package/dist/core/events.d.ts
CHANGED
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
* not depend on `@jini-ai/protocol` (or, by the same reasoning, on `@jini-ai/daemon`), the same
|
|
18
18
|
* "translate the wire, don't import it" posture `assistant-ag-ui.ts` documents for its own port of
|
|
19
19
|
* this package's vocabulary in the other direction. Keep in sync by hand if the daemon's shape
|
|
20
|
-
* changes —
|
|
20
|
+
* changes — the image arm has two required and two optional fields, so drift is easy to spot in review.
|
|
21
21
|
*/
|
|
22
22
|
export type ToolResultMediaBlock = {
|
|
23
23
|
readonly type: 'text';
|
|
@@ -26,6 +26,10 @@ export type ToolResultMediaBlock = {
|
|
|
26
26
|
readonly type: 'image';
|
|
27
27
|
readonly mimeType: string;
|
|
28
28
|
readonly data: string;
|
|
29
|
+
/** Optional caption and alt text (e.g. the media item's title). */
|
|
30
|
+
readonly title?: string;
|
|
31
|
+
/** Optional link to the full file; the renderer only links a safe (http(s) or same-site) target. */
|
|
32
|
+
readonly href?: string;
|
|
29
33
|
};
|
|
30
34
|
/**
|
|
31
35
|
* A single renderable unit of agent output. Covers every generic variant a
|
package/dist/core/messages.d.ts
CHANGED
|
@@ -25,6 +25,10 @@ export interface ChatAttachment {
|
|
|
25
25
|
/** User-visible attachment order for this turn; older items may omit it. */
|
|
26
26
|
order?: number;
|
|
27
27
|
}
|
|
28
|
+
export interface SecretRedactionSignal {
|
|
29
|
+
readonly secretRedacted: true;
|
|
30
|
+
readonly count: number;
|
|
31
|
+
}
|
|
28
32
|
/**
|
|
29
33
|
* A single turn in a conversation. This is the generic subset of OD's
|
|
30
34
|
* `ChatMessage` (`packages/contracts/src/api/chat.ts`): product-shaped
|
|
@@ -36,7 +40,7 @@ export interface ChatMessage {
|
|
|
36
40
|
/** A normal send stays outside recall until the transport accepts it. */
|
|
37
41
|
inputPending?: boolean;
|
|
38
42
|
/** Transient safe receipt; storage requires no new column. */
|
|
39
|
-
secretRedaction?:
|
|
43
|
+
secretRedaction?: SecretRedactionSignal;
|
|
40
44
|
id: string;
|
|
41
45
|
role: ChatRole;
|
|
42
46
|
content: string;
|
|
@@ -38,14 +38,22 @@ function scanJsonPrefix({ buf }, _optional = {}) {
|
|
|
38
38
|
({ inStr, esc } = advanceJsonString({ c, esc }));
|
|
39
39
|
continue;
|
|
40
40
|
}
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
41
|
+
switch (c) {
|
|
42
|
+
case '"':
|
|
43
|
+
inStr = true;
|
|
44
|
+
break;
|
|
45
|
+
case '{':
|
|
46
|
+
stack.push('}');
|
|
47
|
+
break;
|
|
48
|
+
case '[':
|
|
49
|
+
stack.push(']');
|
|
50
|
+
break;
|
|
51
|
+
case '}':
|
|
52
|
+
case ']':
|
|
53
|
+
stack.pop();
|
|
54
|
+
break;
|
|
55
|
+
default: break;
|
|
56
|
+
}
|
|
49
57
|
}
|
|
50
58
|
return { stack, inStr };
|
|
51
59
|
}
|
|
@@ -76,7 +84,8 @@ function trimIncompleteTail({ out }, _optional = {}) {
|
|
|
76
84
|
let prev;
|
|
77
85
|
let passes = 0;
|
|
78
86
|
do {
|
|
79
|
-
|
|
87
|
+
passes += 1;
|
|
88
|
+
if (passes > MAX_TAIL_CLEANUP_PASSES)
|
|
80
89
|
return null;
|
|
81
90
|
prev = out;
|
|
82
91
|
out = trimTrailingSeparators({ text: out });
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
* Ported from Open Design's `summarizeProjectNameFromPrompt` (`apps/web/src/utils/projectName.ts`).
|
|
14
14
|
* The Latin path has since diverged: it keeps the prompt's words, names and dotted domains (the
|
|
15
15
|
* original's stop-word removal and title casing turned "port example.com to jini" into "Port
|
|
16
|
-
* Example Com Jini"), and `
|
|
16
|
+
* Example Com Jini"), and `stripLeadingLatinFiller` fixes the original's article bug, documented at
|
|
17
17
|
* its declaration. The CJK path keeps the original's filler and punctuation rules, which is not
|
|
18
18
|
* decoration: they differ enough from the Latin ones that a shared path produced worse titles in both.
|
|
19
19
|
*/
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
* Ported from Open Design's `summarizeProjectNameFromPrompt` (`apps/web/src/utils/projectName.ts`).
|
|
14
14
|
* The Latin path has since diverged: it keeps the prompt's words, names and dotted domains (the
|
|
15
15
|
* original's stop-word removal and title casing turned "port example.com to jini" into "Port
|
|
16
|
-
* Example Com Jini"), and `
|
|
16
|
+
* Example Com Jini"), and `stripLeadingLatinFiller` fixes the original's article bug, documented at
|
|
17
17
|
* its declaration. The CJK path keeps the original's filler and punctuation rules, which is not
|
|
18
18
|
* decoration: they differ enough from the Latin ones that a shared path produced worse titles in both.
|
|
19
19
|
*/
|
|
@@ -38,7 +38,16 @@ const CJK_PATTERN = /[㐀-鿿]/;
|
|
|
38
38
|
* with `a` after a verb silently lost its first character. Requiring the separator as part of the
|
|
39
39
|
* article makes an article an article rather than a prefix.
|
|
40
40
|
*/
|
|
41
|
-
|
|
41
|
+
function stripLeadingLatinFiller(input) {
|
|
42
|
+
const polite = input.replace(/^please\s+/i, '');
|
|
43
|
+
const request = polite.replace(/^(?:can\s+you|could\s+you|help\s+me|i\s+want\s+to|i\s+need\s+to)\s+/i, '');
|
|
44
|
+
const verb = /^(?:create|build|make|design|implement|add|fix|update|improve|optimize|generate|write|search|find|show|list|turn)\s+/i.exec(request);
|
|
45
|
+
if (!verb)
|
|
46
|
+
return input;
|
|
47
|
+
return request.slice(verb[0].length)
|
|
48
|
+
.replace(/^(?:this|that)\s+into\s+/i, '')
|
|
49
|
+
.replace(/^(?:an|a|the|this|that|my|me)\s+/i, '');
|
|
50
|
+
}
|
|
42
51
|
const LEADING_CJK_FILLER = [
|
|
43
52
|
/^先?(帮我|帮忙|麻烦|请|可以|能不能|能否|给我|我想要|我要)/,
|
|
44
53
|
/^(先)?(实现|做|做一下|创建|生成|设计|开发|新增|添加|优化|修复|改|更改|调整)(一下|一个|一版|下)?/,
|
|
@@ -72,11 +81,11 @@ export function stripConversationTitleMarkdown({ text }, _options = {}) {
|
|
|
72
81
|
.replace(/^\s{0,3}\[[^\]\n]+\]:[^\n]*(?:\n|$)/gm, ' ')
|
|
73
82
|
.replace(/!?\[([^\[\]\n]+)\]\((?:[^()[\]\n]|\([^()[\]\n]*\))*(?:\)|$)/g, '$1')
|
|
74
83
|
.replace(/\[([^\[\]\n]+)\]\[[^\[\]\n]*(?:\]|$)/g, '$1')
|
|
75
|
-
.replace(/^\s{0,3}#{1,6}\s+|[ \t]+#+[ \t]*$/gm, '')
|
|
84
|
+
.replace(/^\s{0,3}#{1,6}\s+|(?<![ \t])[ \t]+#+[ \t]*$/gm, '')
|
|
76
85
|
.replace(/(?<![\w\\*])(\*\*|\*)(?=[^\s*])([^*\n]*?[^\s*\\])\1(?![\w*])/g, '$2')
|
|
77
|
-
.replace(/(?<![\w\\
|
|
86
|
+
.replace(/(?<![\w\\])(__|_)(?=[^\s_])([^\n]*?[^\s\\])\1(?!\w)/g, '$2')
|
|
78
87
|
.replace(/`|~~/g, '')
|
|
79
|
-
.
|
|
88
|
+
.split('\n').map(stripUnclosedTitleLink).join('\n')
|
|
80
89
|
// Unescape after markup removal so an escaped literal marker cannot become emphasis.
|
|
81
90
|
.replace(/\\([\\`*_[\]~])/g, '$1')
|
|
82
91
|
.replace(/\s+/g, ' ')
|
|
@@ -97,7 +106,7 @@ function truncateTitle(title) {
|
|
|
97
106
|
return (lastSpace > 0 ? prefix.slice(0, lastSpace) : prefix).trimEnd();
|
|
98
107
|
}
|
|
99
108
|
function trimLatinTitle(input) {
|
|
100
|
-
const words = input
|
|
109
|
+
const words = stripLeadingLatinFiller(input).match(/[\p{L}\p{N}]+(?:[._-][\p{L}\p{N}]+)*/gu) ?? [];
|
|
101
110
|
const title = words.join(' ');
|
|
102
111
|
// Domain and filename tokens retain their original case, even at the start of a sentence.
|
|
103
112
|
return words[0]?.includes('.') ? title : title.charAt(0).toUpperCase() + title.slice(1);
|
|
@@ -107,7 +116,7 @@ function trimCjkTitle(input) {
|
|
|
107
116
|
for (const pattern of LEADING_CJK_FILLER)
|
|
108
117
|
title = title.replace(pattern, '').trim();
|
|
109
118
|
// Cut at the first sentence-ending punctuation, then drop spacing — CJK titles do not use it.
|
|
110
|
-
title = title.
|
|
119
|
+
title = title.split(/[,。!?;:,.!?;:]/, 1)[0].replace(/\s+/g, '');
|
|
111
120
|
return title.slice(0, MAX_CJK_LENGTH);
|
|
112
121
|
}
|
|
113
122
|
/**
|
|
@@ -144,4 +153,13 @@ export function conversationTitleForDisplay({ title, titleSource, firstPrompt },
|
|
|
144
153
|
}
|
|
145
154
|
return truncateTitle(stripConversationTitleMarkdown({ text: title })) || null;
|
|
146
155
|
}
|
|
156
|
+
/** The first open label after the last closing bracket is the only possible trailing cut link.
|
|
157
|
+
* Retain any later '[' characters inside its label, as the original tolerant pattern does. */
|
|
158
|
+
function stripUnclosedTitleLink(line) {
|
|
159
|
+
const open = line.indexOf('[', line.lastIndexOf(']') + 1);
|
|
160
|
+
if (open === -1)
|
|
161
|
+
return line;
|
|
162
|
+
const start = line[open - 1] === '!' ? open - 1 : open;
|
|
163
|
+
return line.slice(0, start) + line.slice(open + 1);
|
|
164
|
+
}
|
|
147
165
|
//# sourceMappingURL=title.js.map
|
|
@@ -135,7 +135,7 @@ const COLOR_KEYWORD = /^[a-z]+$/i;
|
|
|
135
135
|
* A colour function. The argument charset deliberately excludes `(`, so no nested function —
|
|
136
136
|
* `var()`, `url()`, `image-set()`, `attr()` — can appear inside one of these either.
|
|
137
137
|
*/
|
|
138
|
-
const COLOR_FUNCTION = /^(?:rgb|rgba|hsl|hsla|hwb|lab|lch|oklab|oklch|color)\(
|
|
138
|
+
const COLOR_FUNCTION = /^(?:rgb|rgba|hsl|hsla|hwb|lab|lch|oklab|oklch|color)\([0-9a-z%.,/\s+-]*\)$/i;
|
|
139
139
|
/**
|
|
140
140
|
* Whether a model-supplied palette entry may be written into a CSS value.
|
|
141
141
|
*
|
|
@@ -197,7 +197,7 @@ export function parsePartialQuestionForm({ input }) {
|
|
|
197
197
|
// body — but only when those backticks are the closing wrapper, not content
|
|
198
198
|
// of a JSON string value still being typed.
|
|
199
199
|
function stripTrailingFence(body) {
|
|
200
|
-
const m =
|
|
200
|
+
const m = /(?<!\s)\s*`{1,3}\s*$/.exec(body);
|
|
201
201
|
if (!m)
|
|
202
202
|
return body;
|
|
203
203
|
const before = body.slice(0, m.index);
|
|
@@ -206,10 +206,12 @@ function stripTrailingFence(body) {
|
|
|
206
206
|
return before;
|
|
207
207
|
}
|
|
208
208
|
function endsInsideJsonString(s) {
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
209
|
+
let i = s.indexOf('"');
|
|
210
|
+
while (i !== -1) {
|
|
211
|
+
const end = jsonStringEnd({ s, start: i });
|
|
212
|
+
if (end === -1)
|
|
212
213
|
return true;
|
|
214
|
+
i = s.indexOf('"', end);
|
|
213
215
|
}
|
|
214
216
|
return false;
|
|
215
217
|
}
|
|
@@ -217,9 +219,11 @@ function endsInsideJsonString(s) {
|
|
|
217
219
|
// quote is at `start`, or -1 when that literal hasn't finished streaming.
|
|
218
220
|
function jsonStringEnd({ s, start }) {
|
|
219
221
|
for (let i = start + 1; i < s.length; i++) {
|
|
220
|
-
if (s[i] === '\\')
|
|
222
|
+
if (s[i] === '\\') {
|
|
221
223
|
i++;
|
|
222
|
-
|
|
224
|
+
continue;
|
|
225
|
+
}
|
|
226
|
+
if (s[i] === '"')
|
|
223
227
|
return i + 1;
|
|
224
228
|
}
|
|
225
229
|
return -1;
|
|
@@ -231,10 +235,12 @@ const DEPTH_DELTA = { '{': 1, '[': 1, '}': -1, ']': -1 };
|
|
|
231
235
|
function completeTopLevelString(body, field) {
|
|
232
236
|
const marker = `"${field}"`;
|
|
233
237
|
let depth = 0;
|
|
234
|
-
|
|
238
|
+
let i = 0;
|
|
239
|
+
while (i < body.length) {
|
|
235
240
|
const c = body[i];
|
|
236
241
|
if (c !== '"') {
|
|
237
242
|
depth += DEPTH_DELTA[c] ?? 0;
|
|
243
|
+
i += 1;
|
|
238
244
|
continue;
|
|
239
245
|
}
|
|
240
246
|
const valueStart = depth === 1 ? keyValueStart({ body, at: i, marker }) : -1;
|
|
@@ -244,7 +250,7 @@ function completeTopLevelString(body, field) {
|
|
|
244
250
|
const end = jsonStringEnd({ s: body, start: i });
|
|
245
251
|
if (end === -1)
|
|
246
252
|
return undefined;
|
|
247
|
-
i = end
|
|
253
|
+
i = end;
|
|
248
254
|
}
|
|
249
255
|
return undefined;
|
|
250
256
|
}
|
|
@@ -322,20 +328,24 @@ function countClosedQuestionObjects(body) {
|
|
|
322
328
|
// it never closes (string-aware so braces inside strings don't count).
|
|
323
329
|
function balancedObjectEnd({ s, start }) {
|
|
324
330
|
let depth = 0;
|
|
325
|
-
|
|
331
|
+
let i = start;
|
|
332
|
+
while (i < s.length) {
|
|
326
333
|
const c = s[i];
|
|
327
334
|
if (c === '"') {
|
|
328
335
|
const end = jsonStringEnd({ s, start: i });
|
|
329
336
|
if (end === -1)
|
|
330
337
|
return -1;
|
|
331
|
-
i = end
|
|
332
|
-
|
|
333
|
-
else if (c === '{') {
|
|
334
|
-
depth++;
|
|
338
|
+
i = end;
|
|
339
|
+
continue;
|
|
335
340
|
}
|
|
336
|
-
|
|
337
|
-
|
|
341
|
+
if (c === '{')
|
|
342
|
+
depth += 1;
|
|
343
|
+
if (c === '}') {
|
|
344
|
+
depth -= 1;
|
|
345
|
+
if (depth === 0)
|
|
346
|
+
return i + 1;
|
|
338
347
|
}
|
|
348
|
+
i += 1;
|
|
339
349
|
}
|
|
340
350
|
return -1;
|
|
341
351
|
}
|
|
@@ -31,7 +31,8 @@ export function withoutSecretQuestionDefaults({ form }) {
|
|
|
31
31
|
return { ...form, questions: form.questions.map(question => {
|
|
32
32
|
if (!isSecretQuestion({ question }))
|
|
33
33
|
return question;
|
|
34
|
-
const
|
|
34
|
+
const safe = { ...question };
|
|
35
|
+
delete safe.defaultValue;
|
|
35
36
|
return safe;
|
|
36
37
|
}) };
|
|
37
38
|
}
|