@jini-ai/chat 0.3.10 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +126 -0
- package/README.md +152 -6
- package/dist/core/ag-ui/entry.d.ts +53 -0
- package/dist/core/ag-ui/entry.js +135 -0
- package/dist/core/agentic/chat-capabilities.d.ts +1 -1
- package/dist/core/agentic/index.d.ts +1 -1
- package/dist/core/agentic/index.js +1 -1
- package/dist/core/assistant-content.d.ts +6 -2
- package/dist/core/assistant-content.js +2 -2
- package/dist/core/compact-events.d.ts +3 -1
- package/dist/core/compact-events.js +1 -1
- package/dist/core/events.d.ts +1 -1
- package/dist/core/events.js +1 -1
- package/dist/core/index.d.ts +3 -3
- package/dist/core/index.js +3 -3
- package/dist/core/messages.d.ts +4 -2
- package/dist/core/messages.js +2 -2
- package/dist/core/partial-json.d.ts +6 -2
- package/dist/core/partial-json.js +3 -3
- package/dist/core/persistence/index.d.ts +1 -1
- package/dist/core/persistence/index.js +1 -1
- package/dist/core/persistence/ports.d.ts +2 -123
- package/dist/core/persistence/title.d.ts +3 -1
- package/dist/core/persistence/title.js +1 -1
- package/dist/core/question-form/format.d.ts +16 -7
- package/dist/core/question-form/format.js +3 -3
- package/dist/core/question-form/normalize.d.ts +7 -2
- package/dist/core/question-form/normalize.js +5 -5
- package/dist/core/question-form/scan.d.ts +18 -6
- package/dist/core/question-form/scan.js +14 -14
- package/dist/core/run-activity.d.ts +16 -4
- package/dist/core/run-activity.js +11 -11
- package/dist/core/run-events/entry.d.ts +121 -0
- package/dist/core/run-events/entry.js +290 -0
- package/dist/core/todos.d.ts +20 -8
- package/dist/core/todos.js +11 -11
- package/dist/core/tool-events.d.ts +3 -1
- package/dist/core/tool-events.js +1 -1
- package/dist/core/tool-output.d.ts +3 -1
- package/dist/core/tool-output.js +1 -1
- package/dist/core/tools.d.ts +14 -3
- package/dist/core/tools.js +3 -3
- package/dist/core/transcript.d.ts +13 -5
- package/dist/core/transcript.js +5 -5
- package/dist/core/util/manifest.d.ts +9 -3
- package/dist/core/util/manifest.js +3 -3
- package/dist/core/util/markdown-context.d.ts +11 -3
- package/dist/core/util/markdown-context.js +3 -3
- package/dist/core/util/markup-attributes.d.ts +3 -1
- package/dist/core/util/markup-attributes.js +1 -1
- package/dist/core/util/parser.d.ts +9 -5
- package/dist/core/util/parser.js +13 -13
- package/dist/core/util/recover.d.ts +6 -2
- package/dist/core/util/recover.js +6 -6
- package/dist/core/util/strip.d.ts +19 -5
- package/dist/core/util/strip.js +17 -17
- package/dist/core/util/validate.d.ts +3 -1
- package/dist/core/util/validate.js +1 -1
- package/dist/embed-widget/browser/page-actions.d.ts +121 -0
- package/dist/embed-widget/browser/page-actions.js +113 -0
- package/dist/embed-widget/browser/session-store.d.ts +44 -0
- package/dist/embed-widget/browser/session-store.js +103 -0
- package/dist/embed-widget/react/embed.d.ts +45 -0
- package/dist/embed-widget/react/embed.js +87 -0
- package/dist/embed-widget/react/header.d.ts +21 -0
- package/dist/embed-widget/react/header.js +18 -0
- package/dist/embed-widget/transports/fetch-sse.d.ts +75 -0
- package/dist/embed-widget/transports/fetch-sse.js +175 -0
- package/dist/react/agent-bridge/frontend-session-bridge.d.ts +15 -3
- package/dist/react/agent-bridge/frontend-session-bridge.js +13 -6
- package/dist/react/artifact-types.d.ts +3 -3
- package/dist/react/artifact-types.js +3 -3
- package/dist/react/components/A2uiSurfaceCard.js +34 -13
- package/dist/react/components/AttachmentPreviewModal.hooks.d.ts +7 -2
- package/dist/react/components/AttachmentPreviewModal.hooks.js +5 -5
- package/dist/react/components/AttachmentPreviewModal.js +2 -2
- package/dist/react/components/AttachmentTray.d.ts +3 -1
- package/dist/react/components/AttachmentTray.js +2 -2
- package/dist/react/components/ChatFab.js +1 -1
- package/dist/react/components/Composer.js +9 -9
- package/dist/react/components/ConversationList.js +1 -1
- package/dist/react/components/McpUiSurfaceCard.d.ts +4 -2
- package/dist/react/components/McpUiSurfaceCard.js +5 -5
- package/dist/react/components/MessageList.js +1 -1
- package/dist/react/components/MessageRow.js +9 -9
- package/dist/react/components/QuestionForm.js +5 -5
- package/dist/react/components/ToolCard.js +9 -9
- package/dist/react/components/composer-discovery.d.ts +29 -8
- package/dist/react/components/composer-discovery.js +10 -10
- package/dist/react/ext-event-renderer-registry.d.ts +15 -5
- package/dist/react/ext-event-renderer-registry.js +4 -4
- package/dist/react/features/chat-pane/components/AgentRuntimePicker.d.ts +3 -1
- package/dist/react/features/chat-pane/components/AgentRuntimePicker.js +3 -3
- package/dist/react/features/chat-pane/components/ChatPane.js +43 -43
- package/dist/react/features/chat-pane/create-daemon-attachment-uploader.d.ts +4 -1
- package/dist/react/features/chat-pane/create-daemon-attachment-uploader.js +5 -35
- package/dist/react/features/chat-pane/create-mcp-ui-tool-caller.d.ts +5 -2
- package/dist/react/features/chat-pane/create-mcp-ui-tool-caller.js +1 -1
- package/dist/react/features/chat-pane/hooks/useAgentRuntimePicker.hooks.d.ts +38 -10
- package/dist/react/features/chat-pane/hooks/useAgentRuntimePicker.hooks.js +15 -15
- package/dist/react/features/chat-pane/hooks/useChatPane.hooks.js +31 -31
- package/dist/react/features/chat-pane/hooks/useChatPaneAgentControl.hooks.d.ts +3 -1
- package/dist/react/features/chat-pane/hooks/useChatPaneAgentControl.hooks.js +3 -3
- package/dist/react/features/chat-pane/hooks/useChatPaneComposerPlaceholder.hooks.d.ts +4 -1
- package/dist/react/features/chat-pane/hooks/useChatPaneComposerPlaceholder.hooks.js +1 -1
- package/dist/react/features/chat-pane/hooks/useChatPaneRuntimeInventory.hooks.d.ts +3 -0
- package/dist/react/features/chat-pane/hooks/useChatPaneRuntimeInventory.hooks.js +80 -38
- package/dist/react/features/chat-pane/hooks/useChatPaneWorkingDirectory.hooks.js +57 -57
- package/dist/react/features/chat-pane/rules.d.ts +13 -4
- package/dist/react/features/chat-pane/rules.js +5 -5
- package/dist/react/features/chat-pane/types.d.ts +1 -1
- package/dist/react/hooks/attachment-preview-cache.d.ts +7 -2
- package/dist/react/hooks/attachment-preview-cache.js +2 -2
- package/dist/react/hooks/composer-draft-cache.d.ts +17 -5
- package/dist/react/hooks/composer-draft-cache.js +6 -6
- package/dist/react/hooks/useArtifactStream.d.ts +5 -1
- package/dist/react/hooks/useArtifactStream.js +3 -3
- package/dist/react/hooks/useChatFabDrag.d.ts +9 -4
- package/dist/react/hooks/useChatFabDrag.js +4 -4
- package/dist/react/hooks/useComposer.js +8 -8
- package/dist/react/hooks/useConversation.js +56 -4
- package/dist/react/hooks/useExtEventGroups.d.ts +5 -1
- package/dist/react/hooks/useExtEventGroups.js +1 -1
- package/dist/react/hooks/useLatestOperation.d.ts +8 -3
- package/dist/react/hooks/useLatestOperation.js +4 -4
- package/dist/react/hooks/usePinnedTodos.d.ts +3 -1
- package/dist/react/hooks/usePinnedTodos.js +3 -3
- package/dist/react/hooks/useQuestionForms.d.ts +7 -2
- package/dist/react/hooks/useQuestionForms.js +7 -7
- package/dist/react/hooks/useRunActivity.d.ts +6 -1
- package/dist/react/hooks/useRunActivity.js +3 -3
- package/dist/react/hooks/useRunStream.d.ts +3 -1
- package/dist/react/hooks/useRunStream.js +1 -1
- package/dist/react/hooks/useToolTimeline.d.ts +3 -1
- package/dist/react/hooks/useToolTimeline.js +3 -3
- package/dist/react/index.d.ts +4 -4
- package/dist/react/index.js +6 -6
- package/dist/react/message-blocks.d.ts +10 -4
- package/dist/react/message-blocks.js +2 -2
- package/dist/react/styles/reference.css +3 -3
- package/dist/react/tool-renderer-registry.d.ts +8 -3
- package/dist/react/tool-renderer-registry.js +3 -3
- package/dist/react/util/defined-props.d.ts +3 -1
- package/dist/react/util/defined-props.js +1 -1
- package/dist/server/run-finalizer/entry.d.ts +47 -0
- package/dist/server/run-finalizer/entry.js +156 -0
- package/dist/server/run-finalizer/ports.d.ts +52 -0
- package/dist/store/contracts/index.d.ts +5 -0
- package/dist/store/contracts/index.js +5 -0
- package/dist/store/errors.d.ts +12 -0
- package/dist/store/errors.js +18 -0
- package/dist/store/index.d.ts +4 -0
- package/dist/store/index.js +2 -0
- package/dist/store/legacy/index.d.ts +3 -0
- package/dist/store/legacy/index.js +2 -0
- package/dist/store/legacy/sqlite/conversations.d.ts +129 -0
- package/dist/store/legacy/sqlite/conversations.js +274 -0
- package/dist/store/legacy/sqlite/index.d.ts +6 -0
- package/dist/store/legacy/sqlite/index.js +5 -0
- package/dist/store/legacy/sqlite/messages.d.ts +156 -0
- package/dist/store/legacy/sqlite/messages.js +246 -0
- package/dist/store/legacy/sqlite/schema.d.ts +3 -0
- package/dist/store/legacy/sqlite/schema.js +40 -0
- package/dist/store/pglite/index.d.ts +21 -0
- package/dist/store/pglite/index.js +17 -0
- package/dist/store/ports.d.ts +121 -0
- package/dist/store/ports.js +2 -0
- package/dist/store/postgres/index.d.ts +21 -0
- package/dist/store/postgres/index.js +17 -0
- package/dist/store/sql/boundary.d.ts +28 -0
- package/dist/store/sql/boundary.js +32 -0
- package/dist/store/sql/cursor.d.ts +24 -0
- package/dist/store/sql/cursor.js +49 -0
- package/dist/store/sql/maintenance.d.ts +9 -0
- package/dist/store/sql/maintenance.js +16 -0
- package/dist/store/sql/store.d.ts +22 -0
- package/dist/store/sql/store.js +340 -0
- package/dist/store/sql/tables.d.ts +34 -0
- package/dist/store/sql/tables.js +2 -0
- package/dist/store/sqlite/index.d.ts +36 -0
- package/dist/store/sqlite/index.js +29 -0
- package/dist/store/sqlite/schema.d.ts +49 -0
- package/dist/store/sqlite/schema.js +80 -0
- package/package.json +211 -14
- package/dist/react/features/model-picker/components/CredentialStatusBadge.d.ts +0 -7
- package/dist/react/features/model-picker/components/CredentialStatusBadge.js +0 -20
- package/dist/react/features/model-picker/components/ModelPicker.d.ts +0 -9
- package/dist/react/features/model-picker/components/ModelPicker.js +0 -34
- package/dist/react/features/model-picker/constants.d.ts +0 -6
- package/dist/react/features/model-picker/constants.js +0 -9
- package/dist/react/features/model-picker/dependencies.d.ts +0 -11
- package/dist/react/features/model-picker/dependencies.js +0 -10
- package/dist/react/features/model-picker/hooks/useModelPicker.hooks.d.ts +0 -27
- package/dist/react/features/model-picker/hooks/useModelPicker.hooks.js +0 -62
- package/dist/react/features/model-picker/index.d.ts +0 -32
- package/dist/react/features/model-picker/index.js +0 -29
- package/dist/react/features/model-picker/ports.d.ts +0 -11
- package/dist/react/features/model-picker/rules.d.ts +0 -26
- package/dist/react/features/model-picker/rules.js +0 -84
- package/dist/react/features/model-picker/types.d.ts +0 -38
- package/dist/react/features/model-picker/types.js +0 -2
- package/dist/react/poc-mcp-ui-official/PocMcpUiCard.d.ts +0 -9
- package/dist/react/poc-mcp-ui-official/PocMcpUiCard.js +0 -29
- package/dist/react/poc-mcp-ui-official/create-poc-resource.d.ts +0 -30
- package/dist/react/poc-mcp-ui-official/create-poc-resource.js +0 -44
- /package/dist/{react/features/model-picker → server/run-finalizer}/ports.js +0 -0
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
import { rows } from '@jini-ai/db/core';
|
|
2
|
+
/**
|
|
3
|
+
* Returns all conversations for a project, newest first, each enriched with message count,
|
|
4
|
+
* the latest assistant run summary, and total accumulated run duration across all terminal runs.
|
|
5
|
+
* @param db Open SQLite database handle.
|
|
6
|
+
* @param projectId The project whose conversations to list.
|
|
7
|
+
*/
|
|
8
|
+
export function listConversations({ db, projectId }) {
|
|
9
|
+
return rows(db
|
|
10
|
+
.prepare(`WITH project_conversations AS (
|
|
11
|
+
SELECT id, project_id AS projectId, title, session_mode AS sessionMode,
|
|
12
|
+
created_at AS createdAt, updated_at AS updatedAt
|
|
13
|
+
FROM conversations
|
|
14
|
+
WHERE project_id = ?
|
|
15
|
+
),
|
|
16
|
+
latest_runs AS (
|
|
17
|
+
SELECT conversation_id AS conversationId,
|
|
18
|
+
run_status AS latestRunStatus,
|
|
19
|
+
started_at AS latestRunStartedAt,
|
|
20
|
+
ended_at AS latestRunEndedAt,
|
|
21
|
+
events_json AS latestRunEventsJson
|
|
22
|
+
FROM (
|
|
23
|
+
SELECT m.conversation_id,
|
|
24
|
+
m.run_status,
|
|
25
|
+
m.started_at,
|
|
26
|
+
m.ended_at,
|
|
27
|
+
m.events_json,
|
|
28
|
+
ROW_NUMBER() OVER (
|
|
29
|
+
PARTITION BY m.conversation_id
|
|
30
|
+
ORDER BY m.position DESC
|
|
31
|
+
) AS rn
|
|
32
|
+
FROM messages m
|
|
33
|
+
JOIN project_conversations c ON c.id = m.conversation_id
|
|
34
|
+
WHERE m.role = 'assistant'
|
|
35
|
+
AND m.run_status IS NOT NULL
|
|
36
|
+
)
|
|
37
|
+
WHERE rn = 1
|
|
38
|
+
),
|
|
39
|
+
message_counts AS (
|
|
40
|
+
SELECT m.conversation_id AS conversationId,
|
|
41
|
+
COUNT(*) AS messageCount
|
|
42
|
+
FROM messages m
|
|
43
|
+
JOIN project_conversations c ON c.id = m.conversation_id
|
|
44
|
+
GROUP BY m.conversation_id
|
|
45
|
+
),
|
|
46
|
+
total_run_durations AS (
|
|
47
|
+
SELECT m.conversation_id AS conversationId,
|
|
48
|
+
SUM(${terminalRunDurationSql('m')}) AS totalDurationMs
|
|
49
|
+
FROM messages m
|
|
50
|
+
JOIN project_conversations c ON c.id = m.conversation_id
|
|
51
|
+
WHERE m.role = 'assistant'
|
|
52
|
+
AND m.run_status IN ('succeeded', 'failed', 'canceled')
|
|
53
|
+
GROUP BY m.conversation_id
|
|
54
|
+
)
|
|
55
|
+
SELECT c.id, c.projectId, c.title, c.sessionMode, c.createdAt, c.updatedAt,
|
|
56
|
+
COALESCE(mc.messageCount, 0) AS messageCount,
|
|
57
|
+
lr.latestRunStatus, lr.latestRunStartedAt,
|
|
58
|
+
lr.latestRunEndedAt, lr.latestRunEventsJson,
|
|
59
|
+
trd.totalDurationMs
|
|
60
|
+
FROM project_conversations c
|
|
61
|
+
LEFT JOIN latest_runs lr ON lr.conversationId = c.id
|
|
62
|
+
LEFT JOIN message_counts mc ON mc.conversationId = c.id
|
|
63
|
+
LEFT JOIN total_run_durations trd ON trd.conversationId = c.id
|
|
64
|
+
ORDER BY c.updatedAt DESC`)
|
|
65
|
+
.all(projectId)).map(normalizeConversation);
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Fetches a single conversation by id with its latest run summary and total run duration.
|
|
69
|
+
* Returns null when no matching conversation exists.
|
|
70
|
+
* @param db Open SQLite database handle.
|
|
71
|
+
* @param id Conversation id.
|
|
72
|
+
*/
|
|
73
|
+
export function getConversation({ db, id }) {
|
|
74
|
+
const r = db
|
|
75
|
+
.prepare(`SELECT id, project_id AS projectId, title, session_mode AS sessionMode,
|
|
76
|
+
created_at AS createdAt, updated_at AS updatedAt,
|
|
77
|
+
(SELECT COUNT(*) FROM messages WHERE conversation_id = conversations.id) AS messageCount
|
|
78
|
+
FROM conversations WHERE id = ?`)
|
|
79
|
+
.get(id);
|
|
80
|
+
if (!r)
|
|
81
|
+
return null;
|
|
82
|
+
return {
|
|
83
|
+
...normalizeConversation(r),
|
|
84
|
+
latestRun: latestConversationRunSummary(db, r.id) ?? undefined,
|
|
85
|
+
...numberProperty('totalDurationMs', totalConversationRunDurationMs(db, r.id)),
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
/** @internal Shapes a raw SQLite row into the canonical conversation object, including run summary. */
|
|
89
|
+
function normalizeConversation(r) {
|
|
90
|
+
const latestRun = conversationRunSummaryFromRow({
|
|
91
|
+
runStatus: r.latestRunStatus,
|
|
92
|
+
startedAt: r.latestRunStartedAt,
|
|
93
|
+
endedAt: r.latestRunEndedAt,
|
|
94
|
+
eventsJson: r.latestRunEventsJson,
|
|
95
|
+
});
|
|
96
|
+
return {
|
|
97
|
+
id: r.id,
|
|
98
|
+
projectId: r.projectId,
|
|
99
|
+
title: r.title ?? null,
|
|
100
|
+
sessionMode: normalizeConversationSessionMode({ value: r.sessionMode }),
|
|
101
|
+
messageCount: Number(r.messageCount),
|
|
102
|
+
createdAt: Number(r.createdAt),
|
|
103
|
+
updatedAt: Number(r.updatedAt),
|
|
104
|
+
...numberProperty('totalDurationMs', r.totalDurationMs),
|
|
105
|
+
latestRun: latestRun ?? undefined,
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Coerces an arbitrary database value to a valid ChatSessionMode, defaulting to 'design'.
|
|
110
|
+
* Upholds the invariant that no conversation ever carries an unknown session mode.
|
|
111
|
+
* @param value Raw value read from the session_mode column.
|
|
112
|
+
* @returns A valid ChatSessionMode: 'chat', 'plan', or 'design'.
|
|
113
|
+
*/
|
|
114
|
+
export function normalizeConversationSessionMode({ value }) {
|
|
115
|
+
return value === 'chat' || value === 'plan' ? value : 'design';
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* @internal Produces a single-key object `{ [key]: number }` only when value is a finite number,
|
|
119
|
+
* omitting the key entirely otherwise. Generic over `key` (rather than plain `string`) so the
|
|
120
|
+
* spread of its return type into a caller's object literal carries a real, statically-known
|
|
121
|
+
* optional property instead of collapsing to an index signature the compiler then can't see
|
|
122
|
+
* through at the call site.
|
|
123
|
+
*/
|
|
124
|
+
function numberProperty(key, value) {
|
|
125
|
+
const n = value == null ? undefined : Number(value);
|
|
126
|
+
return typeof n === 'number' && Number.isFinite(n)
|
|
127
|
+
? { [key]: n }
|
|
128
|
+
: {};
|
|
129
|
+
}
|
|
130
|
+
/** @internal Queries the most recent assistant run summary for a conversation (used by getConversation's per-row fetch path). */
|
|
131
|
+
function latestConversationRunSummary(db, conversationId) {
|
|
132
|
+
const row = db
|
|
133
|
+
.prepare(`SELECT run_status AS runStatus,
|
|
134
|
+
started_at AS startedAt,
|
|
135
|
+
ended_at AS endedAt,
|
|
136
|
+
events_json AS eventsJson
|
|
137
|
+
FROM messages
|
|
138
|
+
WHERE conversation_id = ?
|
|
139
|
+
AND role = 'assistant'
|
|
140
|
+
AND run_status IS NOT NULL
|
|
141
|
+
ORDER BY position DESC
|
|
142
|
+
LIMIT 1`)
|
|
143
|
+
.get(conversationId);
|
|
144
|
+
return conversationRunSummaryFromRow(row);
|
|
145
|
+
}
|
|
146
|
+
/** @internal Sums terminal-run duration across all succeeded/failed/canceled assistant messages in a conversation. */
|
|
147
|
+
function totalConversationRunDurationMs(db, conversationId) {
|
|
148
|
+
const row = db
|
|
149
|
+
.prepare(`SELECT SUM(${terminalRunDurationSql()}) AS totalDurationMs
|
|
150
|
+
FROM messages
|
|
151
|
+
WHERE conversation_id = ?
|
|
152
|
+
AND role = 'assistant'
|
|
153
|
+
AND run_status IN ('succeeded', 'failed', 'canceled')`)
|
|
154
|
+
.get(conversationId);
|
|
155
|
+
return row?.totalDurationMs == null ? undefined : Number(row.totalDurationMs);
|
|
156
|
+
}
|
|
157
|
+
/** @internal Emits the SQL CASE expression that resolves a terminal run's duration: wall-clock diff when both timestamps exist, otherwise the latest usage event's durationMs. */
|
|
158
|
+
function terminalRunDurationSql(alias) {
|
|
159
|
+
const p = alias ? `${alias}.` : '';
|
|
160
|
+
return `CASE
|
|
161
|
+
WHEN ${p}started_at IS NOT NULL AND ${p}ended_at IS NOT NULL THEN
|
|
162
|
+
CASE
|
|
163
|
+
WHEN CAST(${p}ended_at AS INTEGER) >= CAST(${p}started_at AS INTEGER)
|
|
164
|
+
THEN CAST(${p}ended_at AS INTEGER) - CAST(${p}started_at AS INTEGER)
|
|
165
|
+
ELSE 0
|
|
166
|
+
END
|
|
167
|
+
ELSE (
|
|
168
|
+
SELECT CASE
|
|
169
|
+
WHEN json_extract(usage_event.value, '$.durationMs') >= 0
|
|
170
|
+
THEN json_extract(usage_event.value, '$.durationMs')
|
|
171
|
+
ELSE 0
|
|
172
|
+
END
|
|
173
|
+
FROM json_each(
|
|
174
|
+
CASE
|
|
175
|
+
WHEN json_valid(${p}events_json) AND json_type(${p}events_json) = 'array'
|
|
176
|
+
THEN ${p}events_json
|
|
177
|
+
ELSE '[]'
|
|
178
|
+
END
|
|
179
|
+
) AS usage_event
|
|
180
|
+
WHERE usage_event.type = 'object'
|
|
181
|
+
AND json_extract(usage_event.value, '$.kind') = 'usage'
|
|
182
|
+
AND json_type(usage_event.value, '$.durationMs') IN ('integer', 'real')
|
|
183
|
+
ORDER BY CAST(usage_event.key AS INTEGER) DESC
|
|
184
|
+
LIMIT 1
|
|
185
|
+
)
|
|
186
|
+
END`;
|
|
187
|
+
}
|
|
188
|
+
/** @internal Converts a raw message row (runStatus/startedAt/endedAt/eventsJson) into a run summary object, or null when no run status is present. */
|
|
189
|
+
function conversationRunSummaryFromRow(row) {
|
|
190
|
+
if (!row || typeof row.runStatus !== 'string')
|
|
191
|
+
return null;
|
|
192
|
+
const startedAt = row.startedAt == null ? undefined : Number(row.startedAt);
|
|
193
|
+
const endedAt = row.endedAt == null ? undefined : Number(row.endedAt);
|
|
194
|
+
const usageDurationMs = latestUsageDurationMs(row.eventsJson);
|
|
195
|
+
const durationMs = Number.isFinite(startedAt) && Number.isFinite(endedAt)
|
|
196
|
+
? Math.max(0, endedAt - startedAt)
|
|
197
|
+
: usageDurationMs;
|
|
198
|
+
return {
|
|
199
|
+
status: row.runStatus,
|
|
200
|
+
...(Number.isFinite(startedAt) ? { startedAt } : {}),
|
|
201
|
+
...(Number.isFinite(endedAt) ? { endedAt } : {}),
|
|
202
|
+
...(typeof durationMs === 'number' && Number.isFinite(durationMs)
|
|
203
|
+
? { durationMs }
|
|
204
|
+
: {}),
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
/** @internal Parses events_json to find the most recent usage event's durationMs; returns undefined when events_json is absent or malformed. */
|
|
208
|
+
function latestUsageDurationMs(eventsJson) {
|
|
209
|
+
if (typeof eventsJson !== 'string' || eventsJson.length === 0)
|
|
210
|
+
return undefined;
|
|
211
|
+
try {
|
|
212
|
+
const events = JSON.parse(eventsJson);
|
|
213
|
+
if (!Array.isArray(events))
|
|
214
|
+
return undefined;
|
|
215
|
+
for (let i = events.length - 1; i >= 0; i -= 1) {
|
|
216
|
+
const event = events[i];
|
|
217
|
+
if (event &&
|
|
218
|
+
typeof event === 'object' &&
|
|
219
|
+
event.kind === 'usage' &&
|
|
220
|
+
typeof event.durationMs === 'number' &&
|
|
221
|
+
Number.isFinite(event.durationMs)) {
|
|
222
|
+
return Math.max(0, event.durationMs);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
catch {
|
|
227
|
+
return undefined;
|
|
228
|
+
}
|
|
229
|
+
return undefined;
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* Inserts a new conversation row and returns the full conversation record (including run summary).
|
|
233
|
+
* Normalizes sessionMode before persisting so the stored value is always a valid ChatSessionMode.
|
|
234
|
+
* @param db Open SQLite database handle.
|
|
235
|
+
* @param c Conversation fields to insert.
|
|
236
|
+
*/
|
|
237
|
+
export function insertConversation({ db, c }) {
|
|
238
|
+
db.prepare(`INSERT INTO conversations
|
|
239
|
+
(id, project_id, title, session_mode, created_at, updated_at)
|
|
240
|
+
VALUES (?, ?, ?, ?, ?, ?)`).run(c.id, c.projectId, c.title ?? null, normalizeConversationSessionMode({ value: c.sessionMode }), c.createdAt, c.updatedAt);
|
|
241
|
+
return getConversation({ db: db, id: c.id });
|
|
242
|
+
}
|
|
243
|
+
/**
|
|
244
|
+
* Applies a partial patch to an existing conversation, merging with the current record.
|
|
245
|
+
* Returns null when the conversation does not exist; otherwise returns the updated record.
|
|
246
|
+
* @param db Open SQLite database handle.
|
|
247
|
+
* @param id Conversation id to update.
|
|
248
|
+
* @param patch Fields to overwrite; sessionMode and updatedAt are normalized before persisting.
|
|
249
|
+
*/
|
|
250
|
+
export function updateConversation({ db, id, patch }) {
|
|
251
|
+
const existing = getConversation({ db: db, id: id });
|
|
252
|
+
if (!existing)
|
|
253
|
+
return null;
|
|
254
|
+
const merged = {
|
|
255
|
+
...existing,
|
|
256
|
+
...patch,
|
|
257
|
+
sessionMode: Object.prototype.hasOwnProperty.call(patch, 'sessionMode')
|
|
258
|
+
? normalizeConversationSessionMode({ value: patch.sessionMode })
|
|
259
|
+
: existing.sessionMode,
|
|
260
|
+
updatedAt: typeof patch.updatedAt === 'number' ? patch.updatedAt : Date.now(),
|
|
261
|
+
};
|
|
262
|
+
db.prepare(`UPDATE conversations
|
|
263
|
+
SET title = ?, session_mode = ?, updated_at = ? WHERE id = ?`).run(merged.title ?? null, merged.sessionMode, merged.updatedAt, id);
|
|
264
|
+
return getConversation({ db: db, id: id });
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* Removes a conversation row by id. Cascading deletes on messages are handled by the schema.
|
|
268
|
+
* @param db Open SQLite database handle.
|
|
269
|
+
* @param id Conversation id to delete.
|
|
270
|
+
*/
|
|
271
|
+
export function deleteConversation({ db, id }) {
|
|
272
|
+
db.prepare(`DELETE FROM conversations WHERE id = ?`).run(id);
|
|
273
|
+
}
|
|
274
|
+
//# sourceMappingURL=conversations.js.map
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/** Sync legacy conversations/messages over a borrowed host handle. */
|
|
2
|
+
export * from './conversations.js';
|
|
3
|
+
export * from './messages.js';
|
|
4
|
+
export { LEGACY_CHAT_DDL } from './schema.js';
|
|
5
|
+
export type { ChatSessionMode } from '../index.js';
|
|
6
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module db/messages/messages
|
|
3
|
+
* Chat message CRUD, streamed-event append, and telemetry finalization. Imports only the
|
|
4
|
+
* core/ kernel (SqliteDb, DbRow, ChatSessionMode, parseJsonOrUndef).
|
|
5
|
+
*
|
|
6
|
+
* NOTE (neutral port): drops six OD-product JSON columns with no generic engine equivalent —
|
|
7
|
+
* `comment_attachments_json` (the preview-comments/canvas-annotation feature, explicitly out of
|
|
8
|
+
* scope for this port), `produced_files_json` and `trace_object_files_json` (design-canvas file
|
|
9
|
+
* output tracking), `feedback_json` (Critique Theater's scoring feedback), `pre_turn_file_names_json`
|
|
10
|
+
* (design-canvas per-turn file context), and `applied_plugin_snapshot_json` (OD's plugin host). The
|
|
11
|
+
* already-ported `schema/migrate.ts` messages table was pre-trimmed to match; see that file and
|
|
12
|
+
* `source-map.md`'s Design Decisions for the same drop list.
|
|
13
|
+
*/
|
|
14
|
+
import type { SqliteDb } from '@jini-ai/db/sqlite';
|
|
15
|
+
import type { DbRow } from '@jini-ai/db/core';
|
|
16
|
+
import type { ChatSessionMode } from '../index.js';
|
|
17
|
+
/**
|
|
18
|
+
* Returns all messages for a conversation in ascending position order, with JSON columns parsed.
|
|
19
|
+
* @param db - Open SQLite connection.
|
|
20
|
+
* @param conversationId - Conversation whose messages to fetch.
|
|
21
|
+
* @returns Normalized message objects ready for API serialization.
|
|
22
|
+
*/
|
|
23
|
+
export declare function listMessages({ db, conversationId }: {
|
|
24
|
+
db: SqliteDb;
|
|
25
|
+
conversationId: string;
|
|
26
|
+
}): {
|
|
27
|
+
id: any;
|
|
28
|
+
role: any;
|
|
29
|
+
content: any;
|
|
30
|
+
agentId: any;
|
|
31
|
+
agentName: any;
|
|
32
|
+
runId: any;
|
|
33
|
+
runStatus: any;
|
|
34
|
+
lastRunEventId: any;
|
|
35
|
+
events: any;
|
|
36
|
+
attachments: any;
|
|
37
|
+
sessionMode: ChatSessionMode | undefined;
|
|
38
|
+
runContext: any;
|
|
39
|
+
createdAt: any;
|
|
40
|
+
startedAt: any;
|
|
41
|
+
endedAt: any;
|
|
42
|
+
}[];
|
|
43
|
+
/**
|
|
44
|
+
* Thrown by `upsertMessage` when the caller presents a message `id` together with a
|
|
45
|
+
* `conversationId` that does not match the conversation the row already belongs to (`id` is a
|
|
46
|
+
* global primary key, not scoped per-conversation, so this is the only signal available). Rather
|
|
47
|
+
* than silently updating the wrong conversation's message — or silently doing nothing — while
|
|
48
|
+
* still bumping the caller-supplied `conversationId`'s `updated_at`, this is a hard failure: it is
|
|
49
|
+
* always a caller/transport bug (a confused id, or a multi-tenant mixup) and never a legitimate
|
|
50
|
+
* state to paper over.
|
|
51
|
+
*/
|
|
52
|
+
export declare class MessageConversationMismatchError extends Error {
|
|
53
|
+
readonly messageId: string;
|
|
54
|
+
readonly actualConversationId: string;
|
|
55
|
+
readonly requestedConversationId: string;
|
|
56
|
+
constructor({ messageId, actualConversationId, requestedConversationId }: {
|
|
57
|
+
messageId: string;
|
|
58
|
+
actualConversationId: string;
|
|
59
|
+
requestedConversationId: string;
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Inserts or updates a message row and bumps the parent conversation's `updated_at` for recency sorting.
|
|
64
|
+
* On insert, appends to the end of the conversation's position sequence.
|
|
65
|
+
* On update, preserves `telemetry_finalized_at` once set (one-way latch).
|
|
66
|
+
*
|
|
67
|
+
* The existence check, position calculation, insert-or-update, and conversation `updated_at` bump
|
|
68
|
+
* all run inside one `better-sqlite3` transaction (opened with `BEGIN IMMEDIATE` via `.immediate()`,
|
|
69
|
+
* not the default deferred `BEGIN`) so the whole read-then-write sequence is atomic: a concurrent
|
|
70
|
+
* connection's own `upsertMessage` call for the same conversation cannot read the pre-write
|
|
71
|
+
* `MAX(position)` while this one is still in flight and thereby compute the same next position —
|
|
72
|
+
* `BEGIN IMMEDIATE` acquires the write lock before this transaction's first read, so a concurrent
|
|
73
|
+
* writer blocks (or fails with `SQLITE_BUSY`, per its own busy-timeout policy) until this one
|
|
74
|
+
* commits, rather than interleaving.
|
|
75
|
+
*
|
|
76
|
+
* `id` is the messages table's global primary key (not scoped per-conversation), so a row can only
|
|
77
|
+
* ever belong to one conversation. If the caller-supplied `m.id` already exists under a different
|
|
78
|
+
* `conversationId`, this throws {@link MessageConversationMismatchError} instead of silently
|
|
79
|
+
* updating the wrong conversation's row (or the wrong conversation's `updated_at`).
|
|
80
|
+
*
|
|
81
|
+
* @param db - Open SQLite connection.
|
|
82
|
+
* @param conversationId - Owning conversation id.
|
|
83
|
+
* @param m - Message fields; JSON sub-fields are stringified automatically.
|
|
84
|
+
* @returns The normalized, re-fetched message row, or null if the row vanished between write and read.
|
|
85
|
+
* @throws {MessageConversationMismatchError} if `m.id` already exists under a different conversation.
|
|
86
|
+
*/
|
|
87
|
+
export declare function upsertMessage({ db, conversationId, m }: {
|
|
88
|
+
db: SqliteDb;
|
|
89
|
+
conversationId: string;
|
|
90
|
+
m: DbRow;
|
|
91
|
+
}): {
|
|
92
|
+
id: any;
|
|
93
|
+
role: any;
|
|
94
|
+
content: any;
|
|
95
|
+
agentId: any;
|
|
96
|
+
agentName: any;
|
|
97
|
+
runId: any;
|
|
98
|
+
runStatus: any;
|
|
99
|
+
lastRunEventId: any;
|
|
100
|
+
events: any;
|
|
101
|
+
attachments: any;
|
|
102
|
+
sessionMode: ChatSessionMode | undefined;
|
|
103
|
+
runContext: any;
|
|
104
|
+
createdAt: any;
|
|
105
|
+
startedAt: any;
|
|
106
|
+
endedAt: any;
|
|
107
|
+
} | null;
|
|
108
|
+
/**
|
|
109
|
+
* Reads whether telemetry has been finalized for a message without loading the full row.
|
|
110
|
+
* Used to decide whether to enqueue a finalization job.
|
|
111
|
+
* @returns `{ exists: false }` when the message is not found; otherwise `{ exists: true, finalizedAt }`.
|
|
112
|
+
*/
|
|
113
|
+
export declare function getMessageTelemetryFinalizationState({ db, messageId }: {
|
|
114
|
+
db: SqliteDb;
|
|
115
|
+
messageId: string;
|
|
116
|
+
}): {
|
|
117
|
+
exists: boolean;
|
|
118
|
+
finalizedAt: number | null;
|
|
119
|
+
};
|
|
120
|
+
/**
|
|
121
|
+
* Appends a `{ kind: 'status', label, detail? }` event to the message's events array.
|
|
122
|
+
* Deduplicates consecutive identical status events so the UI does not stutter on repeated flushes.
|
|
123
|
+
* @param db - Open SQLite connection.
|
|
124
|
+
* @param messageId - Target message id.
|
|
125
|
+
* @param event - Must carry a non-empty `label`; `detail` is optional.
|
|
126
|
+
* @returns The updated events array, or null if the message does not exist or label is empty.
|
|
127
|
+
*/
|
|
128
|
+
export declare function appendMessageStatusEvent({ db, messageId, event }: {
|
|
129
|
+
db: SqliteDb;
|
|
130
|
+
messageId: string;
|
|
131
|
+
event: DbRow;
|
|
132
|
+
}): any[] | null;
|
|
133
|
+
/**
|
|
134
|
+
* Appends an arbitrary agent event to the message's events array, also streaming `text` deltas
|
|
135
|
+
* into the `content` column so the message text grows incrementally during a run.
|
|
136
|
+
* Deduplicates byte-identical consecutive events.
|
|
137
|
+
* @param db - Open SQLite connection.
|
|
138
|
+
* @param messageId - Target message id.
|
|
139
|
+
* @param event - Must carry a non-empty `kind`; `text` field is appended to content when kind is 'text'.
|
|
140
|
+
* @returns The updated events array, or null if the message does not exist or kind is empty.
|
|
141
|
+
*/
|
|
142
|
+
export declare function appendMessageAgentEvent({ db, messageId, event }: {
|
|
143
|
+
db: SqliteDb;
|
|
144
|
+
messageId: string;
|
|
145
|
+
event: DbRow;
|
|
146
|
+
}): any[] | null;
|
|
147
|
+
/**
|
|
148
|
+
* Hard-deletes a single message row by id.
|
|
149
|
+
* @param db - Open SQLite connection.
|
|
150
|
+
* @param id - Message id to remove.
|
|
151
|
+
*/
|
|
152
|
+
export declare function deleteMessage({ db, id }: {
|
|
153
|
+
db: SqliteDb;
|
|
154
|
+
id: string;
|
|
155
|
+
}): void;
|
|
156
|
+
//# sourceMappingURL=messages.d.ts.map
|