@jini-ai/chat 0.6.6 → 0.6.10
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 +129 -1
- package/dist/core/accepted-input.d.ts +54 -0
- package/dist/core/accepted-input.js +165 -0
- package/dist/core/agent-plugin-chat-state.d.ts +43 -0
- package/dist/core/agent-plugin-chat-state.js +48 -0
- package/dist/core/assistant-client/a2ui-action-poster.js +32 -28
- package/dist/core/assistant-client/messages.d.ts +1 -1
- package/dist/core/assistant-client/messages.js +2 -2
- package/dist/core/assistant-client/transport.d.ts +2 -2
- package/dist/core/assistant-client/transport.js +187 -98
- package/dist/core/assistant-content.d.ts +2 -1
- package/dist/core/assistant-content.js +9 -11
- package/dist/core/browser/page-actions.js +4 -2
- package/dist/core/composer-history.d.ts +16 -9
- package/dist/core/composer-history.js +28 -16
- package/dist/core/conversation-title.d.ts +3 -0
- package/dist/core/conversation-title.js +3 -0
- package/dist/core/durable-projection.js +20 -13
- package/dist/core/durable-runs/continuation.d.ts +4 -0
- package/dist/core/durable-runs/continuation.js +31 -6
- package/dist/core/durable-runs/ledger-contracts.d.ts +2 -1
- package/dist/core/durable-runs/ports.d.ts +6 -2
- package/dist/core/index.d.ts +2 -0
- package/dist/core/index.js +2 -0
- package/dist/core/messages.d.ts +2 -0
- package/dist/core/partial-json.d.ts +6 -7
- package/dist/core/partial-json.js +81 -47
- package/dist/core/persistence/title.js +4 -2
- package/dist/core/question-answer.d.ts +23 -0
- package/dist/core/question-answer.js +66 -0
- package/dist/core/question-form/format.d.ts +6 -6
- package/dist/core/question-form/format.js +16 -17
- package/dist/core/question-form/normalize.d.ts +18 -0
- package/dist/core/question-form/normalize.js +112 -122
- package/dist/core/question-form/scan.js +141 -203
- package/dist/core/run-activity.js +62 -49
- package/dist/core/run-events/entry.d.ts +10 -1
- package/dist/core/run-events/entry.js +117 -87
- package/dist/core/todos.d.ts +1 -1
- package/dist/core/todos.js +60 -85
- package/dist/core/tool-events.js +14 -12
- package/dist/core/transcript.js +62 -16
- package/dist/core/turn-usage.js +54 -32
- package/dist/core/user-text-redaction.js +3 -0
- package/dist/core/util/manifest.js +77 -82
- package/dist/core/util/markdown-context.js +51 -53
- package/dist/core/util/parser.js +136 -144
- package/dist/core/util/pointer.js +16 -16
- package/dist/core/util/strip.js +57 -67
- package/dist/core/util/validate.js +15 -17
- package/dist/embed-widget/browser/session-store.js +21 -12
- package/dist/embed-widget/transports/fetch-sse.js +108 -95
- package/dist/embed-widget/vanilla/index.js +81 -70
- package/dist/react/agent-bridge/frontend-session-bridge.js +80 -52
- package/dist/react/components/AttachmentPreviewModal.js +25 -1
- package/dist/react/components/ChatSettingsDialog.d.ts +13 -0
- package/dist/react/components/ChatSettingsDialog.hooks.d.ts +115 -0
- package/dist/react/components/ChatSettingsDialog.hooks.js +40 -0
- package/dist/react/components/ChatSettingsDialog.js +72 -0
- package/dist/react/components/ChatSettingsSegmented.d.ts +18 -0
- package/dist/react/components/ChatSettingsSegmented.js +11 -0
- package/dist/react/components/Composer.d.ts +5 -1
- package/dist/react/components/Composer.js +126 -82
- package/dist/react/components/ConversationList.js +25 -39
- package/dist/react/components/ConversationTitleEditor.d.ts +27 -0
- package/dist/react/components/ConversationTitleEditor.js +54 -0
- package/dist/react/components/Icon.d.ts +1 -1
- package/dist/react/components/Icon.js +25 -45
- package/dist/react/components/Markdown.js +134 -123
- package/dist/react/components/McpUiSurfaceCard.d.ts +2 -2
- package/dist/react/components/McpUiSurfaceCard.js +30 -9
- package/dist/react/components/MessageList.d.ts +13 -13
- package/dist/react/components/MessageList.hooks.d.ts +6 -1
- package/dist/react/components/MessageList.hooks.js +19 -3
- package/dist/react/components/MessageList.js +34 -10
- package/dist/react/components/MessageRow.d.ts +1 -1
- package/dist/react/components/MessageRow.js +75 -47
- package/dist/react/components/MessageRow.usage.js +16 -11
- package/dist/react/components/MidRunUserMessage.js +16 -3
- package/dist/react/components/QuestionForm.js +148 -76
- package/dist/react/components/QuestionsPanel.js +1 -1
- package/dist/react/components/TodoCard.js +12 -6
- package/dist/react/components/ToolCallGroup.d.ts +4 -3
- package/dist/react/components/ToolCallGroup.js +7 -5
- package/dist/react/components/ToolCard.js +47 -40
- package/dist/react/components/composer-discovery.js +6 -1
- package/dist/react/components/mcp-ui-surface-state.d.ts +6 -11
- package/dist/react/components/mcp-ui-surface-state.js +23 -8
- package/dist/react/ext-event-renderer-registry.d.ts +8 -0
- package/dist/react/ext-event-renderer-registry.js +5 -2
- package/dist/react/features/chat-pane/components/AgentPluginUsage.d.ts +7 -0
- package/dist/react/features/chat-pane/components/AgentPluginUsage.js +45 -0
- package/dist/react/features/chat-pane/components/AgentRuntimePicker.js +55 -19
- package/dist/react/features/chat-pane/components/ChatPane.d.ts +1 -1
- package/dist/react/features/chat-pane/components/ChatPane.js +192 -74
- package/dist/react/features/chat-pane/components/RuntimeDropUp.d.ts +29 -0
- package/dist/react/features/chat-pane/components/RuntimeDropUp.js +45 -0
- package/dist/react/features/chat-pane/create-daemon-attachment-uploader.js +80 -47
- package/dist/react/features/chat-pane/create-mcp-ui-tool-caller.js +44 -40
- package/dist/react/features/chat-pane/create-mid-run-message-poster.d.ts +6 -6
- package/dist/react/features/chat-pane/create-mid-run-message-poster.js +8 -8
- package/dist/react/features/chat-pane/create-typed-answer-poster.d.ts +3 -3
- package/dist/react/features/chat-pane/create-typed-answer-poster.js +3 -3
- package/dist/react/features/chat-pane/hooks/useAgentRuntimePicker.hooks.d.ts +6 -1
- package/dist/react/features/chat-pane/hooks/useAgentRuntimePicker.hooks.js +11 -5
- package/dist/react/features/chat-pane/hooks/useChatPane.hooks.d.ts +12 -7
- package/dist/react/features/chat-pane/hooks/useChatPane.hooks.js +340 -149
- package/dist/react/features/chat-pane/hooks/useChatPaneControlsHeight.hooks.js +7 -6
- package/dist/react/features/chat-pane/hooks/useChatPaneNewTabLinks.hooks.js +28 -10
- package/dist/react/features/chat-pane/hooks/useChatPaneRuntimeInventory.hooks.d.ts +3 -3
- package/dist/react/features/chat-pane/hooks/useChatPaneRuntimeInventory.hooks.js +60 -12
- package/dist/react/features/chat-pane/hooks/useRuntimeDropUp.hooks.d.ts +49 -0
- package/dist/react/features/chat-pane/hooks/useRuntimeDropUp.hooks.js +143 -0
- package/dist/react/features/chat-pane/model-options.d.ts +14 -0
- package/dist/react/features/chat-pane/model-options.js +45 -25
- package/dist/react/features/chat-pane/rules.d.ts +8 -9
- package/dist/react/features/chat-pane/rules.js +84 -46
- package/dist/react/features/chat-pane/styles.d.ts +1 -1
- package/dist/react/features/chat-pane/styles.js +152 -36
- package/dist/react/features/chat-pane/types.d.ts +19 -9
- package/dist/react/hooks/assistant-chats.js +12 -5
- package/dist/react/hooks/composer-draft-cache.js +116 -118
- package/dist/react/hooks/composer-history-storage.js +5 -5
- package/dist/react/hooks/fab-position.js +7 -4
- package/dist/react/hooks/useAnsweredCards.d.ts +21 -4
- package/dist/react/hooks/useAnsweredCards.js +38 -11
- package/dist/react/hooks/useArtifactStream.js +28 -22
- package/dist/react/hooks/useComposer.d.ts +20 -0
- package/dist/react/hooks/useComposer.js +22 -9
- package/dist/react/hooks/useComposerHistory.hooks.d.ts +6 -1
- package/dist/react/hooks/useComposerHistory.hooks.js +39 -19
- package/dist/react/hooks/useComposerHistoryKeys.hooks.js +24 -14
- package/dist/react/hooks/useConversation.d.ts +7 -0
- package/dist/react/hooks/useConversation.js +23 -10
- package/dist/react/hooks/useExtEventGroups.js +39 -39
- package/dist/react/hooks/useQuestionForms.js +21 -26
- package/dist/react/hooks/useRunStream.js +10 -2
- package/dist/react/index.d.ts +7 -1
- package/dist/react/index.js +9 -0
- package/dist/react/message-blocks.d.ts +6 -4
- package/dist/react/message-blocks.js +94 -72
- package/dist/react/slots.d.ts +2 -2
- package/dist/react/tool-call-display.d.ts +12 -6
- package/dist/react/tool-call-display.js +53 -24
- package/dist/server/durable-runs/recover.js +132 -80
- package/dist/server/run-finalizer/entry.js +25 -15
- package/dist/store/legacy/sqlite/conversations.d.ts +11 -11
- package/dist/store/legacy/sqlite/conversations.js +69 -98
- package/dist/store/legacy/sqlite/messages.d.ts +10 -2
- package/dist/store/legacy/sqlite/messages.js +113 -71
- package/dist/store/runtime/chat-history-store.js +9 -3
- package/dist/store/runtime/durable-run-store.d.ts +2 -2
- package/dist/store/runtime/durable-run-store.js +165 -117
- package/dist/store/runtime/history-tools.js +118 -82
- package/dist/store/runtime/run-ledger.d.ts +4 -1
- package/dist/store/runtime/run-ledger.js +56 -16
- package/dist/store/sql/cursor.js +38 -18
- package/dist/store/sql/store.d.ts +18 -0
- package/dist/store/sql/store.js +46 -15
- package/package.json +18 -16
package/dist/core/turn-usage.js
CHANGED
|
@@ -19,13 +19,16 @@ export function replySegments({ events }, _optional = {}) {
|
|
|
19
19
|
started = false;
|
|
20
20
|
continue;
|
|
21
21
|
}
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
segments[target].push(event);
|
|
22
|
+
const lateResult = event.kind === 'usage' && !started && segments.length > 1;
|
|
23
|
+
started ||= producesReplyOutput({ event });
|
|
24
|
+
segments[segments.length - (lateResult ? 2 : 1)].push(event);
|
|
26
25
|
}
|
|
27
26
|
return segments;
|
|
28
27
|
}
|
|
28
|
+
/** Usage, status and raw lines are bookkeeping; anything else means the reply has produced output. */
|
|
29
|
+
function producesReplyOutput({ event }, _optional = {}) {
|
|
30
|
+
return event.kind !== 'usage' && event.kind !== 'status' && event.kind !== 'raw';
|
|
31
|
+
}
|
|
29
32
|
/** Resolve one charge against this agent/session's last snapshot. State is local to an
|
|
30
33
|
* ordered transcript projection; revisiting a session keeps its baseline. A lower snapshot
|
|
31
34
|
* starts a new counter epoch (some CLIs reset their counter on resume). O(1) map access. */
|
|
@@ -57,17 +60,21 @@ export function turnUsage({ events }, { sessions = new Map(), agentId = '' } = {
|
|
|
57
60
|
if (event.kind !== 'usage')
|
|
58
61
|
continue;
|
|
59
62
|
total ??= { kind: 'usage' };
|
|
60
|
-
|
|
61
|
-
const value = event[field];
|
|
62
|
-
if (typeof value === 'number' && Number.isFinite(value) && value >= 0)
|
|
63
|
-
total[field] = (total[field] ?? 0) + value;
|
|
64
|
-
}
|
|
63
|
+
addReportedCounts({ total, event });
|
|
65
64
|
const cost = usageCost({ event, agentId, sessions });
|
|
66
65
|
if (cost !== undefined)
|
|
67
66
|
total.costUsd = (total.costUsd ?? 0) + cost;
|
|
68
67
|
}
|
|
69
68
|
return total;
|
|
70
69
|
}
|
|
70
|
+
/** Add an event's well-formed token and duration counts into `total`, in place. */
|
|
71
|
+
function addReportedCounts({ total, event }, _optional = {}) {
|
|
72
|
+
for (const field of ['inputTokens', 'outputTokens', 'durationMs']) {
|
|
73
|
+
const value = event[field];
|
|
74
|
+
if (typeof value === 'number' && Number.isFinite(value) && value >= 0)
|
|
75
|
+
total[field] = (total[field] ?? 0) + value;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
71
78
|
/** Add one reply's usage into a message total; fields absent from both stay absent. O(1). */
|
|
72
79
|
function addUsage({ total, usage }, _optional = {}) {
|
|
73
80
|
if (!usage)
|
|
@@ -92,30 +99,45 @@ export function conversationUsage({ messages }, _optional = {}) {
|
|
|
92
99
|
for (const message of messages) {
|
|
93
100
|
if (message.role !== 'assistant')
|
|
94
101
|
continue;
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
const segment = turnUsage({ events }, { sessions, agentId: message.agentId ?? '' });
|
|
100
|
-
usage = addUsage({ total: usage, usage: segment });
|
|
101
|
-
if (segment?.costUsd !== undefined)
|
|
102
|
-
totalCostUsd = (totalCostUsd ?? 0) + segment.costUsd;
|
|
103
|
-
segments.push(segment && { ...segment, ...(totalCostUsd === undefined ? {} : { totalCostUsd }) });
|
|
104
|
-
}
|
|
105
|
-
if (!usage)
|
|
106
|
-
continue;
|
|
107
|
-
// Tokens-only runtimes still have run timestamps; those cannot time individual mid-run replies.
|
|
108
|
-
if (segments.length === 1 && usage.durationMs === undefined && message.startedAt !== undefined && message.endedAt !== undefined) {
|
|
109
|
-
const durationMs = message.endedAt - message.startedAt;
|
|
110
|
-
if (Number.isFinite(durationMs) && durationMs >= 0)
|
|
111
|
-
usage.durationMs = durationMs;
|
|
112
|
-
}
|
|
113
|
-
summaries.set(message.id, {
|
|
114
|
-
...usage,
|
|
115
|
-
...(totalCostUsd === undefined ? {} : { totalCostUsd }),
|
|
116
|
-
...(segments.length > 1 ? { segments } : {}),
|
|
117
|
-
});
|
|
102
|
+
const result = messageUsage({ message, sessions, totalCostUsd });
|
|
103
|
+
totalCostUsd = result.totalCostUsd;
|
|
104
|
+
if (result.usage)
|
|
105
|
+
summaries.set(message.id, messageSummary({ ...result, usage: result.usage, message }));
|
|
118
106
|
}
|
|
119
107
|
return summaries;
|
|
120
108
|
}
|
|
109
|
+
/** A message's summary: its usage, the prefix total, and per-reply segments when it holds several replies. */
|
|
110
|
+
function messageSummary({ usage, segments, totalCostUsd, message }, _optional = {}) {
|
|
111
|
+
// Tokens-only runtimes still have run timestamps; those cannot time individual mid-run replies.
|
|
112
|
+
if (segments.length === 1)
|
|
113
|
+
fillRunDuration({ usage, message });
|
|
114
|
+
return {
|
|
115
|
+
...usage,
|
|
116
|
+
...(totalCostUsd === undefined ? {} : { totalCostUsd }),
|
|
117
|
+
...(segments.length > 1 ? { segments } : {}),
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
/** One assistant message's summed usage and per-reply summaries, advancing the conversation's prefix total. */
|
|
121
|
+
function messageUsage({ message, sessions, totalCostUsd }, _optional = {}) {
|
|
122
|
+
// Replies run in event order through one session map, so their sum equals the whole-message charge.
|
|
123
|
+
const segments = [];
|
|
124
|
+
let usage;
|
|
125
|
+
let prefixTotal = totalCostUsd;
|
|
126
|
+
for (const events of replySegments({ events: message.events })) {
|
|
127
|
+
const segment = turnUsage({ events }, { sessions, agentId: message.agentId ?? '' });
|
|
128
|
+
usage = addUsage({ total: usage, usage: segment });
|
|
129
|
+
if (segment?.costUsd !== undefined)
|
|
130
|
+
prefixTotal = (prefixTotal ?? 0) + segment.costUsd;
|
|
131
|
+
segments.push(segment && { ...segment, ...(prefixTotal === undefined ? {} : { totalCostUsd: prefixTotal }) });
|
|
132
|
+
}
|
|
133
|
+
return { usage, segments, totalCostUsd: prefixTotal };
|
|
134
|
+
}
|
|
135
|
+
/** Use the message's elapsed run time when no reply reported a duration; invalid timestamps stay unknown. */
|
|
136
|
+
function fillRunDuration({ usage, message }, _optional = {}) {
|
|
137
|
+
if (usage.durationMs !== undefined || message.startedAt === undefined || message.endedAt === undefined)
|
|
138
|
+
return;
|
|
139
|
+
const durationMs = message.endedAt - message.startedAt;
|
|
140
|
+
if (Number.isFinite(durationMs) && durationMs >= 0)
|
|
141
|
+
usage.durationMs = durationMs;
|
|
142
|
+
}
|
|
121
143
|
//# sourceMappingURL=turn-usage.js.map
|
|
@@ -7,6 +7,9 @@ const INTERNAL_REMOVED = '[REDACTED:chat_secret]';
|
|
|
7
7
|
const REDACTION_MARKER = /\[REDACTED:[^\]\r\n]+\]/g;
|
|
8
8
|
const CHAT_RULES = [
|
|
9
9
|
{ kind: 'chat_key', pattern: /(?<![A-Za-z0-9_-])sk-[A-Za-z0-9_-]{16,}/g, replacement: () => INTERNAL_REMOVED },
|
|
10
|
+
// Kept on top of the shared Slack rule: that rule refuses a token preceded by `-` (e.g. a URL path
|
|
11
|
+
// segment `/-xoxb-…`) and the entropy fallback below exempts URLs, so without this rule such a token
|
|
12
|
+
// would reach storage and model history. Only drop it once the shared rule covers that case.
|
|
10
13
|
{ kind: 'chat_key', pattern: /(?<![A-Za-z0-9_])xox[baprs]-[A-Za-z0-9-]{16,}/g, replacement: () => INTERNAL_REMOVED },
|
|
11
14
|
// Explicit authorization syntax catches even short values. Exempt scheme descriptions in prose.
|
|
12
15
|
{ kind: 'chat_bearer', pattern: /\bBearer[ \t]+(?!\[REDACTED)(?!(?:token|tokens|authentication|authorization|auth|scheme|credentials?)\b)[A-Za-z0-9._~+\/=-]+/gi, replacement: () => 'Bearer ' + INTERNAL_REMOVED },
|
|
@@ -27,33 +27,27 @@ function normalizeExt(name) {
|
|
|
27
27
|
const i = name.lastIndexOf('.');
|
|
28
28
|
return i >= 0 ? name.slice(i).toLowerCase() : '';
|
|
29
29
|
}
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
if (kind === 'svg' || kind === 'diagram')
|
|
52
|
-
return ['svg', 'zip'];
|
|
53
|
-
if (kind === 'code-snippet')
|
|
54
|
-
return ['txt', 'zip'];
|
|
55
|
-
return ['html', 'pdf', 'zip'];
|
|
56
|
-
}
|
|
30
|
+
const LEGACY_KIND_BY_EXT = new Map([
|
|
31
|
+
['.html', 'html'],
|
|
32
|
+
['.htm', 'html'],
|
|
33
|
+
['.svg', 'svg'],
|
|
34
|
+
['.md', 'markdown-document'],
|
|
35
|
+
['.jsx', 'react-component'],
|
|
36
|
+
['.tsx', 'react-component'],
|
|
37
|
+
['.js', 'code-snippet'],
|
|
38
|
+
['.ts', 'code-snippet'],
|
|
39
|
+
['.json', 'code-snippet'],
|
|
40
|
+
['.css', 'code-snippet'],
|
|
41
|
+
]);
|
|
42
|
+
/** Renderer and export set for every kind `inferLegacyManifest` can resolve to (the inferred kinds plus the deck-name heuristic's `deck`). */
|
|
43
|
+
const LEGACY_KIND_INFO = {
|
|
44
|
+
html: { renderer: 'html', exports: ['html', 'pdf', 'zip'] },
|
|
45
|
+
deck: { renderer: 'deck-html', exports: ['html', 'pdf', 'zip'] },
|
|
46
|
+
'markdown-document': { renderer: 'markdown', exports: ['md', 'html', 'pdf', 'zip'] },
|
|
47
|
+
'react-component': { renderer: 'react-component', exports: ['jsx', 'html', 'zip'] },
|
|
48
|
+
'code-snippet': { renderer: 'code', exports: ['txt', 'zip'] },
|
|
49
|
+
svg: { renderer: 'svg', exports: ['svg', 'zip'] }, // 'svg' is a valid ArtifactRendererId as-is
|
|
50
|
+
};
|
|
57
51
|
/** The manifest sidecar file name convention for a given artifact entry file name. */
|
|
58
52
|
export function artifactManifestNameFor({ entry }) {
|
|
59
53
|
return `${entry}.artifact.json`;
|
|
@@ -90,50 +84,62 @@ export function serializeArtifactManifest({ manifest }) {
|
|
|
90
84
|
* @complexity O(k) in the manifest's own field/array count — no I/O.
|
|
91
85
|
*/
|
|
92
86
|
export function parseArtifactManifest({ raw }) {
|
|
87
|
+
let parsed;
|
|
93
88
|
try {
|
|
94
|
-
|
|
95
|
-
if (parsed?.version !== MANIFEST_VERSION)
|
|
96
|
-
return null;
|
|
97
|
-
if (typeof parsed.entry !== 'string' || !parsed.entry)
|
|
98
|
-
return null;
|
|
99
|
-
if (typeof parsed.title !== 'string' || !parsed.title)
|
|
100
|
-
return null;
|
|
101
|
-
if (!Array.isArray(parsed.exports))
|
|
102
|
-
return null;
|
|
103
|
-
if (typeof parsed.kind !== 'string' || typeof parsed.renderer !== 'string') {
|
|
104
|
-
return null;
|
|
105
|
-
}
|
|
106
|
-
if (!ALLOWED_KINDS.has(parsed.kind))
|
|
107
|
-
return null;
|
|
108
|
-
if (!ALLOWED_RENDERERS.has(parsed.renderer))
|
|
109
|
-
return null;
|
|
110
|
-
if (parsed.status !== undefined && !ALLOWED_STATUS.has(parsed.status)) {
|
|
111
|
-
return null;
|
|
112
|
-
}
|
|
113
|
-
if (parsed.exports.length === 0)
|
|
114
|
-
return null;
|
|
115
|
-
if (parsed.exports.some((value) => !ALLOWED_EXPORTS.has(value)))
|
|
116
|
-
return null;
|
|
117
|
-
return {
|
|
118
|
-
version: MANIFEST_VERSION,
|
|
119
|
-
kind: parsed.kind,
|
|
120
|
-
title: parsed.title,
|
|
121
|
-
entry: parsed.entry,
|
|
122
|
-
renderer: parsed.renderer,
|
|
123
|
-
status: ALLOWED_STATUS.has(parsed.status) ? parsed.status : 'complete',
|
|
124
|
-
exports: parsed.exports,
|
|
125
|
-
primary: parsed.primary === true || typeof parsed.primary === 'string' ? parsed.primary : undefined,
|
|
126
|
-
supportingFiles: Array.isArray(parsed.supportingFiles) ? parsed.supportingFiles.filter((x) => typeof x === 'string') : undefined,
|
|
127
|
-
createdAt: typeof parsed.createdAt === 'string' ? parsed.createdAt : undefined,
|
|
128
|
-
updatedAt: typeof parsed.updatedAt === 'string' ? parsed.updatedAt : undefined,
|
|
129
|
-
sourceSkillId: typeof parsed.sourceSkillId === 'string' ? parsed.sourceSkillId : undefined,
|
|
130
|
-
designSystemId: typeof parsed.designSystemId === 'string' || parsed.designSystemId === null ? parsed.designSystemId : undefined,
|
|
131
|
-
metadata: parsed.metadata && typeof parsed.metadata === 'object' && !Array.isArray(parsed.metadata) ? parsed.metadata : undefined,
|
|
132
|
-
};
|
|
89
|
+
parsed = JSON.parse(raw);
|
|
133
90
|
}
|
|
134
91
|
catch {
|
|
135
92
|
return null;
|
|
136
93
|
}
|
|
94
|
+
if (!hasRequiredManifestFields(parsed) || !hasKnownManifestValues(parsed))
|
|
95
|
+
return null;
|
|
96
|
+
return {
|
|
97
|
+
version: MANIFEST_VERSION,
|
|
98
|
+
kind: parsed.kind,
|
|
99
|
+
title: parsed.title,
|
|
100
|
+
entry: parsed.entry,
|
|
101
|
+
renderer: parsed.renderer,
|
|
102
|
+
status: parsed.status ?? 'complete',
|
|
103
|
+
exports: parsed.exports,
|
|
104
|
+
...optionalManifestFields(parsed),
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
/** The optional manifest fields, each kept only when it has its documented type (anything else is dropped, not rejected). */
|
|
108
|
+
function optionalManifestFields(parsed) {
|
|
109
|
+
return {
|
|
110
|
+
primary: parsed.primary === true || typeof parsed.primary === 'string' ? parsed.primary : undefined,
|
|
111
|
+
supportingFiles: Array.isArray(parsed.supportingFiles) ? parsed.supportingFiles.filter(isString) : undefined,
|
|
112
|
+
createdAt: stringOrUndefined(parsed.createdAt),
|
|
113
|
+
updatedAt: stringOrUndefined(parsed.updatedAt),
|
|
114
|
+
sourceSkillId: stringOrUndefined(parsed.sourceSkillId),
|
|
115
|
+
designSystemId: parsed.designSystemId === null ? null : stringOrUndefined(parsed.designSystemId),
|
|
116
|
+
metadata: parsed.metadata && typeof parsed.metadata === 'object' && !Array.isArray(parsed.metadata) ? parsed.metadata : undefined,
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
function hasRequiredManifestFields(parsed) {
|
|
120
|
+
if (parsed?.version !== MANIFEST_VERSION)
|
|
121
|
+
return false;
|
|
122
|
+
if (!isNonEmptyString(parsed.entry) || !isNonEmptyString(parsed.title))
|
|
123
|
+
return false;
|
|
124
|
+
return Array.isArray(parsed.exports) && isString(parsed.kind) && isString(parsed.renderer);
|
|
125
|
+
}
|
|
126
|
+
function hasKnownManifestValues(parsed) {
|
|
127
|
+
if (!ALLOWED_KINDS.has(parsed.kind))
|
|
128
|
+
return false;
|
|
129
|
+
if (!ALLOWED_RENDERERS.has(parsed.renderer))
|
|
130
|
+
return false;
|
|
131
|
+
if (parsed.status !== undefined && !ALLOWED_STATUS.has(parsed.status))
|
|
132
|
+
return false;
|
|
133
|
+
return parsed.exports.length > 0 && parsed.exports.every((value) => ALLOWED_EXPORTS.has(value));
|
|
134
|
+
}
|
|
135
|
+
function isString(value) {
|
|
136
|
+
return typeof value === 'string';
|
|
137
|
+
}
|
|
138
|
+
function isNonEmptyString(value) {
|
|
139
|
+
return isString(value) && value !== '';
|
|
140
|
+
}
|
|
141
|
+
function stringOrUndefined(value) {
|
|
142
|
+
return isString(value) ? value : undefined;
|
|
137
143
|
}
|
|
138
144
|
/**
|
|
139
145
|
* Infer a manifest for a legacy artifact file that predates manifest
|
|
@@ -141,31 +147,20 @@ export function parseArtifactManifest({ raw }) {
|
|
|
141
147
|
* @returns `null` when the extension isn't a recognized artifact kind.
|
|
142
148
|
*/
|
|
143
149
|
export function inferLegacyManifest(input) {
|
|
144
|
-
const kind =
|
|
150
|
+
const kind = LEGACY_KIND_BY_EXT.get(normalizeExt(input.entry));
|
|
145
151
|
if (!kind)
|
|
146
152
|
return null;
|
|
147
|
-
const
|
|
148
|
-
const isDeck = kind === 'html' && (lowerEntry.includes('deck') || lowerEntry.includes('slides') || lowerEntry.includes('pitch'));
|
|
149
|
-
const renderer = isDeck
|
|
150
|
-
? 'deck-html'
|
|
151
|
-
: kind === 'html'
|
|
152
|
-
? 'html'
|
|
153
|
-
: kind === 'markdown-document'
|
|
154
|
-
? 'markdown'
|
|
155
|
-
: kind === 'react-component'
|
|
156
|
-
? 'react-component'
|
|
157
|
-
: kind === 'code-snippet'
|
|
158
|
-
? 'code'
|
|
159
|
-
: kind; // only 'svg' remains — a valid ArtifactRendererId as-is
|
|
153
|
+
const isDeck = kind === 'html' && /deck|slides|pitch/.test(input.entry.toLowerCase());
|
|
160
154
|
const resolvedKind = isDeck ? 'deck' : kind;
|
|
155
|
+
const info = LEGACY_KIND_INFO[resolvedKind];
|
|
161
156
|
return {
|
|
162
157
|
version: MANIFEST_VERSION,
|
|
163
158
|
kind: resolvedKind,
|
|
164
159
|
title: input.title || input.entry,
|
|
165
160
|
entry: input.entry,
|
|
166
|
-
renderer,
|
|
161
|
+
renderer: info.renderer,
|
|
167
162
|
status: 'complete',
|
|
168
|
-
exports:
|
|
163
|
+
exports: [...info.exports],
|
|
169
164
|
primary: resolvedKind === 'html' || resolvedKind === 'deck' ? true : undefined,
|
|
170
165
|
metadata: input.metadata,
|
|
171
166
|
};
|
|
@@ -53,66 +53,64 @@ export function isRealArtifactOpenAt({ content, idx }) {
|
|
|
53
53
|
* backtick scan per accumulated paragraph block.
|
|
54
54
|
*/
|
|
55
55
|
export function computeSkipRanges({ buffer }) {
|
|
56
|
-
const
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
56
|
+
const scan = { ranges: [], blockRegions: [], fenceStart: -1, blockStart: -1 };
|
|
57
|
+
for (const line of linesOf(buffer)) {
|
|
58
|
+
if (scan.fenceStart === -1)
|
|
59
|
+
visitLineOutsideFence(scan, line);
|
|
60
|
+
else
|
|
61
|
+
visitLineInsideFence(scan, line);
|
|
62
|
+
}
|
|
63
|
+
const unclosedFenceStart = scan.fenceStart === -1 ? null : scan.fenceStart;
|
|
64
|
+
if (unclosedFenceStart === null)
|
|
65
|
+
closeBlockBefore(scan, buffer.length); // flush an open paragraph region at end-of-buffer
|
|
66
|
+
for (const [s, e] of scan.blockRegions) {
|
|
67
|
+
for (const m of buffer.slice(s, e).matchAll(INLINE_CODE_RE)) {
|
|
68
|
+
scan.ranges.push([s + m.index, s + m.index + m[0].length]);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return { ranges: scan.ranges, unclosedFenceStart };
|
|
72
|
+
}
|
|
73
|
+
function* linesOf(buffer) {
|
|
61
74
|
let pos = 0;
|
|
62
|
-
let inFence = false;
|
|
63
|
-
let fenceStart = -1;
|
|
64
|
-
let blockStart = -1;
|
|
65
|
-
const closeBlockBefore = (idx) => {
|
|
66
|
-
if (blockStart !== -1 && idx > blockStart)
|
|
67
|
-
blockRegions.push([blockStart, idx]);
|
|
68
|
-
blockStart = -1;
|
|
69
|
-
};
|
|
70
75
|
while (pos < buffer.length) {
|
|
71
76
|
const eol = buffer.indexOf('\n', pos);
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
if (lineHasNewline && FENCE_OPEN_RE.test(line)) {
|
|
77
|
-
closeBlockBefore(pos);
|
|
78
|
-
inFence = true;
|
|
79
|
-
fenceStart = pos;
|
|
80
|
-
}
|
|
81
|
-
else if (line.trim() === '') {
|
|
82
|
-
closeBlockBefore(pos);
|
|
83
|
-
}
|
|
84
|
-
else if (HEADING_RE.test(line) || UL_ITEM_RE.test(line) || OL_ITEM_RE.test(line)) {
|
|
85
|
-
// Each heading/list-item line is its own inline-scan region rather
|
|
86
|
-
// than joining adjacent paragraphs.
|
|
87
|
-
closeBlockBefore(pos);
|
|
88
|
-
blockRegions.push([pos, lineEnd]);
|
|
89
|
-
}
|
|
90
|
-
else {
|
|
91
|
-
if (blockStart === -1)
|
|
92
|
-
blockStart = pos;
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
else if (lineHasNewline && FENCE_CLOSE_RE.test(line)) {
|
|
96
|
-
inFence = false;
|
|
97
|
-
ranges.push([fenceStart, eol + 1]);
|
|
98
|
-
fenceStart = -1;
|
|
77
|
+
if (eol === -1) {
|
|
78
|
+
// trailing partial line — classified like any other, but never as a fence delimiter
|
|
79
|
+
yield { start: pos, end: buffer.length, text: buffer.slice(pos), hasNewline: false };
|
|
80
|
+
return;
|
|
99
81
|
}
|
|
100
|
-
|
|
101
|
-
break; // trailing partial line — already accounted for above
|
|
82
|
+
yield { start: pos, end: eol, text: buffer.slice(pos, eol), hasNewline: true };
|
|
102
83
|
pos = eol + 1;
|
|
103
84
|
}
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
85
|
+
}
|
|
86
|
+
function closeBlockBefore(scan, idx) {
|
|
87
|
+
if (scan.blockStart !== -1 && idx > scan.blockStart)
|
|
88
|
+
scan.blockRegions.push([scan.blockStart, idx]);
|
|
89
|
+
scan.blockStart = -1;
|
|
90
|
+
}
|
|
91
|
+
function visitLineInsideFence(scan, line) {
|
|
92
|
+
if (!line.hasNewline || !FENCE_CLOSE_RE.test(line.text))
|
|
93
|
+
return;
|
|
94
|
+
scan.ranges.push([scan.fenceStart, line.end + 1]);
|
|
95
|
+
scan.fenceStart = -1;
|
|
96
|
+
}
|
|
97
|
+
function visitLineOutsideFence(scan, line) {
|
|
98
|
+
if (line.hasNewline && FENCE_OPEN_RE.test(line.text)) {
|
|
99
|
+
closeBlockBefore(scan, line.start);
|
|
100
|
+
scan.fenceStart = line.start;
|
|
101
|
+
}
|
|
102
|
+
else if (line.text.trim() === '') {
|
|
103
|
+
closeBlockBefore(scan, line.start);
|
|
104
|
+
}
|
|
105
|
+
else if (HEADING_RE.test(line.text) || UL_ITEM_RE.test(line.text) || OL_ITEM_RE.test(line.text)) {
|
|
106
|
+
// Each heading/list-item line is its own inline-scan region rather
|
|
107
|
+
// than joining adjacent paragraphs.
|
|
108
|
+
closeBlockBefore(scan, line.start);
|
|
109
|
+
scan.blockRegions.push([line.start, line.end]);
|
|
110
|
+
}
|
|
111
|
+
else if (scan.blockStart === -1) {
|
|
112
|
+
scan.blockStart = line.start;
|
|
114
113
|
}
|
|
115
|
-
return { ranges, unclosedFenceStart: inFence ? fenceStart : null };
|
|
116
114
|
}
|
|
117
115
|
/** `true` when position `p` falls inside one of `ranges` (half-open intervals). */
|
|
118
116
|
export function rangeContains({ ranges, p }) {
|