@ai-matrx/chat 0.4.9 → 0.4.12
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 +53 -0
- package/dist/agents/components/context-items/bodies/ContextInputBody.js +1 -1
- package/dist/agents/components/context-items/bodies/GenericBody.js +1 -1
- package/dist/agents/components/conversation-actions/conversation-verbs.js +1 -1
- package/dist/agents/components/inputs/smart-input/RunSkillPicker.js +1 -1
- package/dist/agents/components/live-run/LiveRunDisplay.js +1 -1
- package/dist/agents/components/live-run/LiveRunProgress.js +1 -1
- package/dist/agents/components/live-run/RunSetDisplay.js +1 -1
- package/dist/agents/components/messages-display/assistant/AgentAssistantMessage.js +2 -2
- package/dist/agents/components/messages-display/assistant/InPlaceAnswerEditor.js +1 -1
- package/dist/agents/components/messages-display/conversation-tools/find-in-conversation.js +1 -1
- package/dist/agents/components/messages-display/message-options/buildTaskSeedFromMessage.js +1 -1
- package/dist/agents/components/messages-display/user/AgentUserMessage.js +1 -1
- package/dist/agents/components/messages-display/user/FirstTurnVariables.js +1 -1
- package/dist/agents/components/run/AgentStatusIndicator.js +1 -1
- package/dist/agents/components/run/RunJobWorkingLine.js +1 -1
- package/dist/agents/components/shared/AgentNewRunButton.js +1 -1
- package/dist/agents/components/shared/AgentPlanningIndicator.js +1 -1
- package/dist/agents/conversation-export/conversation-transfer.js +1 -1
- package/dist/agents/conversation-export/document-markdown.js +1 -1
- package/dist/agents/hooks/useConversationRoutePromotion.js +1 -1
- package/dist/agents/hooks/useModelControls.js +1 -1
- package/dist/agents/redux/agent-definition/converters.js +1 -1
- package/dist/agents/redux/agent-definition/slice.js +1 -1
- package/dist/agents/redux/conversation-list/conversation-list.thunks.js +1 -1
- package/dist/agents/redux/execution-system/active-requests/active-requests.selectors.js +1 -1
- package/dist/agents/redux/execution-system/instance-context/sync-canvas-item-context.thunk.js +1 -1
- package/dist/agents/redux/execution-system/thunks/process-stream.js +1 -1
- package/dist/agents/redux/execution-system/thunks/resolve-base-url.js +17 -0
- package/dist/agents/redux/execution-system/thunks/run-ai-stream.d.ts +7 -0
- package/dist/agents/redux/execution-system/thunks/run-ai-stream.js +13 -0
- package/dist/agents/redux/execution-system/thunks/run-headless-agent-json.js +14 -2
- package/dist/agents/redux/execution-system/utils/assemble-cx-content-blocks.js +1 -1
- package/dist/agents/redux/execution-system/utils/normalize-content-blocks.js +1 -1
- package/dist/agents/redux/execution-system/utils/output-contract-guard.js +1 -1
- package/dist/agents/redux/execution-system/utils/stream-block-accumulator.js +8 -8
- package/dist/agents/routing/confidential-routing.d.ts +52 -0
- package/dist/agents/routing/confidential-routing.js +38 -0
- package/dist/agents/utils/canvasItemContext.js +1 -1
- package/dist/canvas/workspace/ChatPanelTitleMenu.js +1 -1
- package/dist/canvas/workspace/useCanvasWorkspaceConversation.js +1 -1
- package/dist/cx-chat/components/SsrSidebarChats.js +1 -1
- package/dist/cx-chat/components/core/ChatConversationClient.js +1 -1
- package/dist/cx-chat/components/messages/AssistantMessage.js +1 -1
- package/dist/cx-chat/hooks/useInstanceBootstrap.js +1 -1
- package/dist/cx-conversation/AssistantMessage.js +1 -1
- package/dist/host/configure.js +4 -1
- package/dist/host/contract.d.ts +14 -1
- package/dist/host/index.d.ts +1 -0
- package/dist/host/index.js +1 -0
- package/dist/mandates/llm-params.d.ts +1 -1
- package/dist/mandates/output-contract.js +1 -1
- package/dist/mandates/service.d.ts +1 -1
- package/dist/mandates/service.js +1 -1
- package/dist/mandates/test-run-core.js +1 -1
- package/dist/public-chat/components/PublicMessageOptionsMenu.js +1 -1
- package/dist/surfaces/services/surface-config.service.js +1 -1
- package/dist/tool-call-visualization/components/ToolCallBatch.js +1 -1
- package/dist/tool-call-visualization/components/ToolCallVisualization.js +1 -1
- package/dist/tool-call-visualization/renderers/agent-call/AgentCallInline.js +1 -1
- package/dist/tool-call-visualization/renderers/agent-call/CollabCallCard.js +1 -1
- package/dist/tool-call-visualization/renderers/agent-call/collab-subtitle.js +3 -3
- package/dist/tool-call-visualization/renderers/skill/SkillInline.js +1 -1
- package/dist/tool-call-visualization/result-fields/KeyValueGrid.js +1 -1
- package/dist/tool-call-visualization/result-fields/ResultValue.js +1 -1
- package/dist/tool-call-visualization/result-fields/shape.js +1 -1
- package/dist/tool-call-visualization/utils/cxToolCallToLifecycleEntry.js +2 -2
- package/dist/tool-call-visualization/utils/human-copy.js +3 -3
- package/dist/ui/mandates-storage/provision-shapes.d.ts +1 -1
- package/dist/ui/mandates-storage/provision-shapes.js +1 -1
- package/dist/ui/markdown-stream/EnhancedChatMarkdown.js +1 -1
- package/dist/ui/treatment-shape.d.ts +1 -1
- package/dist/ui/treatment-shape.js +1 -1
- package/dist/voice-agent/relay/useVoiceRelaySession.js +1 -1
- package/package.json +26 -9
- package/dist/agents/hooks/useWarmAgent.d.ts +0 -8
- package/dist/agents/hooks/useWarmAgent.js +0 -25
- package/dist/agents/utils/artifactId.d.ts +0 -34
- package/dist/agents/utils/artifactId.js +0 -15
- package/dist/ui/ShimmerText.d.ts +0 -5
- package/dist/ui/ShimmerText.js +0 -21
- package/dist/ui/addressWithoutNavigating.d.ts +0 -68
- package/dist/ui/addressWithoutNavigating.js +0 -60
- package/dist/utils/content-ir/decision-answers-ids.d.ts +0 -3
- package/dist/utils/content-ir/decision-answers-ids.js +0 -6
- package/dist/utils/content-ir/envelope-parity.d.ts +0 -12
- package/dist/utils/content-ir/envelope-parity.js +0 -32
- package/dist/utils/content-ir/instance-title.d.ts +0 -26
- package/dist/utils/content-ir/instance-title.js +0 -39
- package/dist/utils/content-ir/kinds/kind-markdown-utils.d.ts +0 -84
- package/dist/utils/content-ir/kinds/kind-markdown-utils.js +0 -233
- package/dist/utils/content-ir/kinds/kind-value-markdown.d.ts +0 -5
- package/dist/utils/content-ir/kinds/kind-value-markdown.js +0 -17
- package/dist/utils/content-ir/legacy-quiz-envelope.d.ts +0 -3
- package/dist/utils/content-ir/legacy-quiz-envelope.js +0 -45
- package/dist/utils/content-ir/surfaces/embedded-kind-json.d.ts +0 -140
- package/dist/utils/content-ir/surfaces/embedded-kind-json.js +0 -527
- package/dist/utils/content-ir/surfaces/json-kind-signal.d.ts +0 -307
- package/dist/utils/content-ir/surfaces/json-kind-signal.js +0 -945
- package/dist/utils/content-ir/surfaces/kind-image-alt.d.ts +0 -27
- package/dist/utils/content-ir/surfaces/kind-image-alt.js +0 -117
- package/dist/utils/content-ir/surfaces/kind-one-line.d.ts +0 -67
- package/dist/utils/content-ir/surfaces/kind-one-line.js +0 -209
- package/dist/utils/content-ir/surfaces/kind-text-label.d.ts +0 -20
- package/dist/utils/content-ir/surfaces/kind-text-label.js +0 -50
- package/dist/utils/content-ir/surfaces/kind-text-to-markdown.d.ts +0 -47
- package/dist/utils/content-ir/surfaces/kind-text-to-markdown.js +0 -399
- package/dist/utils/content-ir/surfaces/markdown-escaped-kind.d.ts +0 -54
- package/dist/utils/content-ir/surfaces/markdown-escaped-kind.js +0 -128
- package/dist/utils/content-ir/surfaces/quoted-kind-lift.d.ts +0 -61
- package/dist/utils/content-ir/surfaces/quoted-kind-lift.js +0 -215
- package/dist/utils/inline-decision/decision-options.d.ts +0 -21
- package/dist/utils/inline-decision/decision-options.js +0 -32
- package/dist/utils/json.d.ts +0 -63
- package/dist/utils/json.js +0 -22
- package/dist/utils/markdown/plain-title.d.ts +0 -25
- package/dist/utils/markdown/plain-title.js +0 -51
- package/dist/utils/rendered-find.d.ts +0 -9
- package/dist/utils/rendered-find.js +0 -68
- package/dist/utils/rich-editor/core/caret-context.d.ts +0 -24
- package/dist/utils/rich-editor/core/caret-context.js +0 -73
- package/dist/utils/rich-editor/in-place/caret-handoff.d.ts +0 -3
- package/dist/utils/rich-editor/in-place/caret-handoff.js +0 -15
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* addressWithoutNavigating — THE one way to change the address bar without a
|
|
3
|
-
* navigation (lane URL-STATE, 2026-09-24; born in lane PANEL-REMOUNT).
|
|
4
|
-
*
|
|
5
|
-
* Query state (`?view=`, `?panels=`, `?tab=`, a selected row, a filter) is
|
|
6
|
-
* bookkeeping about the page you are on, never a new page. `router.replace` /
|
|
7
|
-
* `router.push` are App Router NAVIGATIONS: each one fetches a fresh RSC
|
|
8
|
-
* payload for the route (a `?_rsc=` request, a server round trip) and, on
|
|
9
|
-
* commit, can remount the page — measured on /data/[tableId]: one `?_rsc=`
|
|
10
|
-
* request per layout switch, and before PANEL-REMOUNT the whole grid re-read.
|
|
11
|
-
*
|
|
12
|
-
* `history.pushState` / `history.replaceState` are patched by Next's app
|
|
13
|
-
* router: called with a state object that does NOT carry `__NA`, the patch
|
|
14
|
-
* copies Next's own tree state onto the new entry and dispatches a `restore`,
|
|
15
|
-
* so `useSearchParams` / `usePathname` hold the new URL with no server round
|
|
16
|
-
* trip and no remount. Back/forward still work (Next owns the entry).
|
|
17
|
-
*
|
|
18
|
-
* 🚨 This module always passes `null`. Passing `window.history.state` (the
|
|
19
|
-
* old habit) forwards Next's `__NA` marker, which SKIPS the patch: the address
|
|
20
|
-
* bar changes while `useSearchParams` keeps the old value.
|
|
21
|
-
*
|
|
22
|
-
* Use the router for a REAL route change (another page), or when the server
|
|
23
|
-
* component of the current route reads the changed param from `searchParams`
|
|
24
|
-
* — only a navigation re-renders server output. Everything else comes here.
|
|
25
|
-
* Guard: eslint `matrx/no-navigation-for-query-state`.
|
|
26
|
-
*/
|
|
27
|
-
/**
|
|
28
|
-
* Replace the current history entry's address. No navigation, no RSC fetch.
|
|
29
|
-
*
|
|
30
|
-
* A HASH-ONLY change skips Next's patch on purpose: nothing Next exposes
|
|
31
|
-
* (`usePathname`, `useSearchParams`, `useParams`) reads the hash, yet the
|
|
32
|
-
* patch's `restore` hands every `useSearchParams` reader a new object, so each
|
|
33
|
-
* write re-rendered all of them. A canvas writing its camera to `#cam=` while
|
|
34
|
-
* panning paid that per write — over a second per pointer move on a board of
|
|
35
|
-
* fifteen heavy tiles, so the board froze while panning (2026-10-01). Forwarding
|
|
36
|
-
* the entry's own state keeps Next's `__NA` tree on it (Back still works).
|
|
37
|
-
*/
|
|
38
|
-
export declare function replaceAddressWithoutNavigating(href: string | URL): void;
|
|
39
|
-
/** Push a new history entry (Back returns to the old address). No navigation, no RSC fetch. */
|
|
40
|
-
export declare function pushAddressWithoutNavigating(href: string | URL): void;
|
|
41
|
-
/**
|
|
42
|
-
* The current path (and hash) carrying `search` as its query — the usual
|
|
43
|
-
* argument for the two writers above. An empty query drops the `?`.
|
|
44
|
-
*/
|
|
45
|
-
export declare function currentPathWithSearch(search: URLSearchParams | string): string;
|
|
46
|
-
/** Just enough of Next's router for the two helpers below (and their tests). */
|
|
47
|
-
export interface AddressRouter {
|
|
48
|
-
push: (href: string, options?: {
|
|
49
|
-
scroll?: boolean;
|
|
50
|
-
}) => void;
|
|
51
|
-
replace: (href: string, options?: {
|
|
52
|
-
scroll?: boolean;
|
|
53
|
-
}) => void;
|
|
54
|
-
}
|
|
55
|
-
/**
|
|
56
|
-
* For a caller that names its page by a literal path (`/tasks?task=…`) but
|
|
57
|
-
* can also render somewhere else (a window panel over another page): on that
|
|
58
|
-
* page the change is query state, so no navigation; anywhere else it is a real
|
|
59
|
-
* route change, so the router carries it exactly as before (`options` are the
|
|
60
|
-
* router's, passed through unchanged).
|
|
61
|
-
*/
|
|
62
|
-
export declare function replaceAddressOrNavigate(router: AddressRouter, href: string, options?: {
|
|
63
|
-
scroll?: boolean;
|
|
64
|
-
}): void;
|
|
65
|
-
/** The push twin of {@link replaceAddressOrNavigate}. */
|
|
66
|
-
export declare function pushAddressOrNavigate(router: AddressRouter, href: string, options?: {
|
|
67
|
-
scroll?: boolean;
|
|
68
|
-
}): void;
|
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
function currentHref() {
|
|
2
|
-
return `${window.location.pathname}${window.location.search}${window.location.hash}`;
|
|
3
|
-
}
|
|
4
|
-
function resolve(href) {
|
|
5
|
-
const url = new URL(String(href), window.location.href);
|
|
6
|
-
if (url.origin !== window.location.origin) {
|
|
7
|
-
throw new Error(
|
|
8
|
-
`addressWithoutNavigating: ${url.origin} is another origin; history writes are same-origin only. Navigate instead.`
|
|
9
|
-
);
|
|
10
|
-
}
|
|
11
|
-
return `${url.pathname}${url.search}${url.hash}`;
|
|
12
|
-
}
|
|
13
|
-
function replaceAddressWithoutNavigating(href) {
|
|
14
|
-
if (typeof window === "undefined") return;
|
|
15
|
-
const next = resolve(href);
|
|
16
|
-
if (next === currentHref()) return;
|
|
17
|
-
const sameDocument = next.split("#")[0] === `${window.location.pathname}${window.location.search}`;
|
|
18
|
-
const state = window.history.state;
|
|
19
|
-
if (sameDocument && isNextEntry(state)) {
|
|
20
|
-
window.history.replaceState(state, "", next);
|
|
21
|
-
return;
|
|
22
|
-
}
|
|
23
|
-
window.history.replaceState(null, "", next);
|
|
24
|
-
}
|
|
25
|
-
function isNextEntry(state) {
|
|
26
|
-
return typeof state === "object" && state !== null && ("__NA" in state || "_N" in state);
|
|
27
|
-
}
|
|
28
|
-
function pushAddressWithoutNavigating(href) {
|
|
29
|
-
if (typeof window === "undefined") return;
|
|
30
|
-
const next = resolve(href);
|
|
31
|
-
if (next === currentHref()) return;
|
|
32
|
-
window.history.pushState(null, "", next);
|
|
33
|
-
}
|
|
34
|
-
function currentPathWithSearch(search) {
|
|
35
|
-
const qs = (typeof search === "string" ? search : search.toString()).replace(
|
|
36
|
-
/^\?/,
|
|
37
|
-
""
|
|
38
|
-
);
|
|
39
|
-
if (typeof window === "undefined") return qs ? `?${qs}` : "";
|
|
40
|
-
return `${window.location.pathname}${qs ? `?${qs}` : ""}${window.location.hash}`;
|
|
41
|
-
}
|
|
42
|
-
function isCurrentPath(href) {
|
|
43
|
-
const url = new URL(String(href), window.location.href);
|
|
44
|
-
return url.origin === window.location.origin && url.pathname === window.location.pathname;
|
|
45
|
-
}
|
|
46
|
-
function replaceAddressOrNavigate(router, href, options) {
|
|
47
|
-
if (typeof window !== "undefined" && isCurrentPath(href)) replaceAddressWithoutNavigating(href);
|
|
48
|
-
else router.replace(href, options);
|
|
49
|
-
}
|
|
50
|
-
function pushAddressOrNavigate(router, href, options) {
|
|
51
|
-
if (typeof window !== "undefined" && isCurrentPath(href)) pushAddressWithoutNavigating(href);
|
|
52
|
-
else router.push(href, options);
|
|
53
|
-
}
|
|
54
|
-
export {
|
|
55
|
-
currentPathWithSearch,
|
|
56
|
-
pushAddressOrNavigate,
|
|
57
|
-
pushAddressWithoutNavigating,
|
|
58
|
-
replaceAddressOrNavigate,
|
|
59
|
-
replaceAddressWithoutNavigating
|
|
60
|
-
};
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Pure envelope helpers the chat package needs (chat-package-move P14): the Phase-2 shadow
|
|
3
|
-
* parity check ("does the envelope, reconstructed, deep-equal what JSON.parse sees?").
|
|
4
|
-
*
|
|
5
|
-
* A COMPARISON, NEVER A TRANSFORM — the one lawful shape of `stripKindDeep` outside the two doors.
|
|
6
|
-
* Markers are removed from BOTH sides, symmetrically, inside this predicate, so a marker the parser
|
|
7
|
-
* INJECTED (speculation / expectedRootKind) is not reported as a mismatch against a source that did
|
|
8
|
-
* not carry one. Only a boolean leaves; neither input is mutated and neither reduced value is ever
|
|
9
|
-
* returned, stored, or rendered (KINDS_EVERYWHERE_PLAN §4.2).
|
|
10
|
-
*/
|
|
11
|
-
import { type CanonicalBlockIR } from "@ai-matrx/content-ir";
|
|
12
|
-
export declare function envelopeMatchesParsedSource(envelope: CanonicalBlockIR, parsedSource: unknown): boolean;
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
import { reconstructRegionValue, stripKindDeep } from "@ai-matrx/content-ir";
|
|
2
|
-
function isRecord(value) {
|
|
3
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
4
|
-
}
|
|
5
|
-
function deepEqual(left, right) {
|
|
6
|
-
if (Object.is(left, right)) return true;
|
|
7
|
-
if (Array.isArray(left) && Array.isArray(right)) {
|
|
8
|
-
if (left.length !== right.length) return false;
|
|
9
|
-
for (let i = 0; i < left.length; i++) {
|
|
10
|
-
if (!deepEqual(left[i], right[i])) return false;
|
|
11
|
-
}
|
|
12
|
-
return true;
|
|
13
|
-
}
|
|
14
|
-
if (isRecord(left) && isRecord(right)) {
|
|
15
|
-
const leftKeys = Object.keys(left);
|
|
16
|
-
const rightKeys = Object.keys(right);
|
|
17
|
-
if (leftKeys.length !== rightKeys.length) return false;
|
|
18
|
-
for (const key of leftKeys) {
|
|
19
|
-
if (!(key in right)) return false;
|
|
20
|
-
if (!deepEqual(left[key], right[key])) return false;
|
|
21
|
-
}
|
|
22
|
-
return true;
|
|
23
|
-
}
|
|
24
|
-
return false;
|
|
25
|
-
}
|
|
26
|
-
function envelopeMatchesParsedSource(envelope, parsedSource) {
|
|
27
|
-
if (!isRecord(parsedSource)) return false;
|
|
28
|
-
return deepEqual(stripKindDeep(reconstructRegionValue(envelope)), stripKindDeep(parsedSource));
|
|
29
|
-
}
|
|
30
|
-
export {
|
|
31
|
-
envelopeMatchesParsedSource
|
|
32
|
-
};
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Instance TITLE derivation — pure, importable from server and client code
|
|
3
|
-
* (no supabase import; `instance-service.ts` consumes it on the write path).
|
|
4
|
-
*
|
|
5
|
-
* CROSS-REPO MIRROR: aidream `kind_instance.py` (`_TITLE_KEYS` +
|
|
6
|
-
* `derive_title` + `kind_shared.kind_title_key`) implements the SAME
|
|
7
|
-
* derivation order — explicit title → the kind's `metadata.title_key` field
|
|
8
|
-
* (per-kind override, non-empty scalar) → the shared key list → null.
|
|
9
|
-
* Change BOTH sides together.
|
|
10
|
-
*/
|
|
11
|
-
/** Mirrors aidream `kind_instance._TITLE_KEYS` — keep the two in lockstep. */
|
|
12
|
-
export declare const INSTANCE_TITLE_KEYS: readonly ["title", "name", "label", "heading", "subject", "customer"];
|
|
13
|
-
/**
|
|
14
|
-
* The kind's per-kind instance-title override —
|
|
15
|
-
* `kind_definition.metadata.title_key` (a single data key naming the title
|
|
16
|
-
* field, e.g. `wine_name`). Set by the creator agent via `kind_create`;
|
|
17
|
-
* null when absent/blank/non-string. Server parity: `kind_shared.kind_title_key`.
|
|
18
|
-
*/
|
|
19
|
-
export declare function kindTitleKeyFromMetadata(metadata: unknown): string | null;
|
|
20
|
-
/**
|
|
21
|
-
* Explicit title wins; else the kind's `metadata.title_key` field when it
|
|
22
|
-
* holds a non-empty scalar; else the first non-empty string under a shared
|
|
23
|
-
* title/name-ish key. The ORDER is a cross-repo contract — mirrored verbatim
|
|
24
|
-
* by aidream `kind_instance.derive_title`.
|
|
25
|
-
*/
|
|
26
|
-
export declare function deriveInstanceTitle(data: Record<string, unknown>, explicit?: string | null, titleKey?: string | null): string | null;
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
const INSTANCE_TITLE_KEYS = [
|
|
2
|
-
"title",
|
|
3
|
-
"name",
|
|
4
|
-
"label",
|
|
5
|
-
"heading",
|
|
6
|
-
"subject",
|
|
7
|
-
"customer"
|
|
8
|
-
];
|
|
9
|
-
function isRecord(value) {
|
|
10
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
11
|
-
}
|
|
12
|
-
function kindTitleKeyFromMetadata(metadata) {
|
|
13
|
-
if (!isRecord(metadata)) return null;
|
|
14
|
-
const value = metadata["title_key"];
|
|
15
|
-
return typeof value === "string" && value.trim() ? value.trim() : null;
|
|
16
|
-
}
|
|
17
|
-
function scalarTitle(value) {
|
|
18
|
-
if (typeof value === "string") return value.trim() || null;
|
|
19
|
-
if (typeof value === "boolean") return value ? "true" : "false";
|
|
20
|
-
if (typeof value === "number") return Number.isFinite(value) ? String(value) : null;
|
|
21
|
-
return null;
|
|
22
|
-
}
|
|
23
|
-
function deriveInstanceTitle(data, explicit, titleKey) {
|
|
24
|
-
if (explicit && explicit.trim()) return explicit.trim();
|
|
25
|
-
if (titleKey) {
|
|
26
|
-
const override = scalarTitle(data[titleKey]);
|
|
27
|
-
if (override) return override;
|
|
28
|
-
}
|
|
29
|
-
for (const key of INSTANCE_TITLE_KEYS) {
|
|
30
|
-
const value = data[key];
|
|
31
|
-
if (typeof value === "string" && value.trim()) return value.trim();
|
|
32
|
-
}
|
|
33
|
-
return null;
|
|
34
|
-
}
|
|
35
|
-
export {
|
|
36
|
-
INSTANCE_TITLE_KEYS,
|
|
37
|
-
deriveInstanceTitle,
|
|
38
|
-
kindTitleKeyFromMetadata
|
|
39
|
-
};
|
|
@@ -1,84 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Shared plumbing for kind → markdown export facets (`toMarkdown`).
|
|
3
|
-
*
|
|
4
|
-
* The FORWARD leg of the artifact ⇄ markdown two-way layer: every facet
|
|
5
|
-
* turns a kind's ZERO-LOSS value object into clean human-readable markdown
|
|
6
|
-
* (headings / lists / bold — never a JSON dump), following two laws:
|
|
7
|
-
*
|
|
8
|
-
* 1. `__kind` discriminators are transport metadata — never rendered.
|
|
9
|
-
* 2. Nothing silently vanishes: keys a facet doesn't understand (plus the
|
|
10
|
-
* declared `additionalDetails` bag every top-level kind schema carries)
|
|
11
|
-
* are appended under a small "Additional details" key: value section via
|
|
12
|
-
* `collectExtras` + `additionalDetailsSection`.
|
|
13
|
-
*
|
|
14
|
-
* `genericKindMarkdown` is the fallback for kinds WITHOUT a `toMarkdown`
|
|
15
|
-
* facet (and for unregistered kinds): readable markdown built from the value
|
|
16
|
-
* itself — headings, bold-label lists, tables — never a JSON dump.
|
|
17
|
-
*/
|
|
18
|
-
export declare function isRecordValue(value: unknown): value is Record<string, unknown>;
|
|
19
|
-
/**
|
|
20
|
-
* One-line rendering of an arbitrary value for key: value lists. Scalars
|
|
21
|
-
* render as text, scalar arrays join with ", ", anything structural falls
|
|
22
|
-
* back to inline JSON in a code span (zero loss, still one line).
|
|
23
|
-
*/
|
|
24
|
-
export declare function formatInlineValue(value: unknown): string;
|
|
25
|
-
/**
|
|
26
|
-
* Collect the keys a facet does NOT understand. Skips `__kind`,
|
|
27
|
-
* null/undefined, and the facet's known keys; merges the contents of a
|
|
28
|
-
* declared `additionalDetails` object bag (the schema-blessed extras
|
|
29
|
-
* channel) into the same flat map so both extra channels surface together.
|
|
30
|
-
*/
|
|
31
|
-
export declare function collectExtras(value: Record<string, unknown>, knownKeys: Iterable<string>): Record<string, unknown>;
|
|
32
|
-
/**
|
|
33
|
-
* A field's own name, as words — `violations_not_fixed` → "Violations not
|
|
34
|
-
* fixed", `wordCountAfter` → "Word count after".
|
|
35
|
-
*
|
|
36
|
-
* 🚨 WALK 18, DEFECT D. The finished Masterwork deliverable an Expert hands a
|
|
37
|
-
* customer carried `violations_not_fixed: []` — a key out of a schema we
|
|
38
|
-
* declared, printed at a person. A label is the one place a field name is
|
|
39
|
-
* GUARANTEED to reach a reader, so the resolution lives here, beside the
|
|
40
|
-
* extras plumbing, for every RENDERER that puts a structured field on a
|
|
41
|
-
* screen: underscores and camel humps become spaces, the first letter is
|
|
42
|
-
* capitalised, and nothing else changes.
|
|
43
|
-
*
|
|
44
|
-
* Deliberately NOT applied inside {@link extrasList}. That list is the
|
|
45
|
-
* markdown/EXPORT leg (`features/canvas/export/exportArtifactMarkdown.ts`
|
|
46
|
-
* writes a file somebody — or something — reads back), and a key spelled as
|
|
47
|
-
* prose cannot be read back as a key. A screen resolves; a file keeps the key.
|
|
48
|
-
*/
|
|
49
|
-
export declare function plainFieldLabel(key: string): string;
|
|
50
|
-
/** Render extras as a key: value bullet list (no heading). Null when empty. */
|
|
51
|
-
export declare function extrasList(extras: Record<string, unknown>): string | null;
|
|
52
|
-
/**
|
|
53
|
-
* The canonical "Additional details" section — appended at the END of a
|
|
54
|
-
* kind's markdown so nothing silently vanishes. Null when there is nothing
|
|
55
|
-
* to say (callers filter with `joinBlocks`).
|
|
56
|
-
*/
|
|
57
|
-
export declare function additionalDetailsSection(extras: Record<string, unknown>, headingLevel?: "##" | "###" | "####"): string | null;
|
|
58
|
-
/** Join markdown blocks with blank lines, dropping empty/null ones. */
|
|
59
|
-
export declare function joinBlocks(blocks: Array<string | null | undefined>): string;
|
|
60
|
-
/** "flashcard_set" → "Flashcard Set"; "Artifact" when there is nothing to say. */
|
|
61
|
-
export declare function humanizeKind(kind: string): string;
|
|
62
|
-
/** Renders a nested kind value as markdown (the registry's converter). */
|
|
63
|
-
export type NestedKindMarkdown = (value: Record<string, unknown>) => string;
|
|
64
|
-
/**
|
|
65
|
-
* Fallback markdown for kinds with no `toMarkdown` facet (or unregistered
|
|
66
|
-
* kinds): READABLE markdown built from the value itself (kind-never-raw,
|
|
67
|
-
* Arman 2026-09-30 — a kind is never shown as raw JSON, an export included).
|
|
68
|
-
* Heading = the instance title (`deriveInstanceTitle`), then the kind's
|
|
69
|
-
* name; scalar fields as a bold-label list; arrays of uniform scalar records
|
|
70
|
-
* as a table, other arrays as nested lists; nested plain objects as
|
|
71
|
-
* sections; nested KINDS through `nested` (the registry converter —
|
|
72
|
-
* `kindValueToMarkdown` passes itself; default: this function). Never the
|
|
73
|
-
* `__kind` key, never a JSON fence. Every field still appears (zero loss in
|
|
74
|
-
* content; the discriminator is named in words by the subtitle).
|
|
75
|
-
*/
|
|
76
|
-
export declare function genericKindMarkdown(kind: string, value: Record<string, unknown>, nested?: NestedKindMarkdown): string;
|
|
77
|
-
/**
|
|
78
|
-
* Readable markdown for a value that is NOT itself a kind — a kindless JSON
|
|
79
|
-
* wrapper around kinds, or a run of plain values beside kinds in an array —
|
|
80
|
-
* so a destination (export, copy) never prints it as JSON. Same rendering as
|
|
81
|
-
* a kind's body in {@link genericKindMarkdown} (bold-label scalars, tables for
|
|
82
|
-
* uniform records, nested lists, sections), nested kinds through `nested`.
|
|
83
|
-
*/
|
|
84
|
-
export declare function plainValueMarkdown(value: unknown, nested?: NestedKindMarkdown): string;
|
|
@@ -1,233 +0,0 @@
|
|
|
1
|
-
import { KIND_KEY } from "@ai-matrx/content-ir";
|
|
2
|
-
import {
|
|
3
|
-
deriveInstanceTitle,
|
|
4
|
-
INSTANCE_TITLE_KEYS
|
|
5
|
-
} from "../instance-title.js";
|
|
6
|
-
import { humanizeIdentifier } from "@ai-matrx/kit/text-case";
|
|
7
|
-
function isRecordValue(value) {
|
|
8
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
9
|
-
}
|
|
10
|
-
function isScalar(value) {
|
|
11
|
-
return typeof value === "string" || typeof value === "number" || typeof value === "boolean";
|
|
12
|
-
}
|
|
13
|
-
function formatInlineValue(value) {
|
|
14
|
-
if (value === null || value === void 0) return "\u2014";
|
|
15
|
-
if (isScalar(value)) return String(value);
|
|
16
|
-
if (Array.isArray(value) && value.every(isScalar)) {
|
|
17
|
-
return value.map(String).join(", ");
|
|
18
|
-
}
|
|
19
|
-
try {
|
|
20
|
-
return `\`${JSON.stringify(stripKindForDisplay(value))}\``;
|
|
21
|
-
} catch {
|
|
22
|
-
return String(value);
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
function stripKindForDisplay(value) {
|
|
26
|
-
if (Array.isArray(value)) return value.map(stripKindForDisplay);
|
|
27
|
-
if (isRecordValue(value)) {
|
|
28
|
-
const out = {};
|
|
29
|
-
for (const [key, child] of Object.entries(value)) {
|
|
30
|
-
if (key === KIND_KEY) continue;
|
|
31
|
-
out[key] = stripKindForDisplay(child);
|
|
32
|
-
}
|
|
33
|
-
return out;
|
|
34
|
-
}
|
|
35
|
-
return value;
|
|
36
|
-
}
|
|
37
|
-
function collectExtras(value, knownKeys) {
|
|
38
|
-
const known = new Set(knownKeys);
|
|
39
|
-
known.add(KIND_KEY);
|
|
40
|
-
known.add("additionalDetails");
|
|
41
|
-
const extras = {};
|
|
42
|
-
const details = value.additionalDetails;
|
|
43
|
-
if (isRecordValue(details)) {
|
|
44
|
-
for (const [key, child] of Object.entries(details)) {
|
|
45
|
-
if (key === KIND_KEY || child === null || child === void 0) continue;
|
|
46
|
-
extras[key] = child;
|
|
47
|
-
}
|
|
48
|
-
} else if (details !== null && details !== void 0) {
|
|
49
|
-
extras.additionalDetails = details;
|
|
50
|
-
}
|
|
51
|
-
for (const [key, child] of Object.entries(value)) {
|
|
52
|
-
if (known.has(key) || child === null || child === void 0) continue;
|
|
53
|
-
extras[key] = child;
|
|
54
|
-
}
|
|
55
|
-
return extras;
|
|
56
|
-
}
|
|
57
|
-
function plainFieldLabel(key) {
|
|
58
|
-
const words = key.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/[_-]+/g, " ").trim().toLowerCase();
|
|
59
|
-
if (!words) return key;
|
|
60
|
-
return words.charAt(0).toUpperCase() + words.slice(1);
|
|
61
|
-
}
|
|
62
|
-
function extrasList(extras) {
|
|
63
|
-
const entries = Object.entries(extras);
|
|
64
|
-
if (entries.length === 0) return null;
|
|
65
|
-
return entries.map(([key, value]) => `- **${key}:** ${formatInlineValue(value)}`).join("\n");
|
|
66
|
-
}
|
|
67
|
-
function additionalDetailsSection(extras, headingLevel = "##") {
|
|
68
|
-
const list = extrasList(extras);
|
|
69
|
-
if (!list) return null;
|
|
70
|
-
return `${headingLevel} Additional details
|
|
71
|
-
|
|
72
|
-
${list}`;
|
|
73
|
-
}
|
|
74
|
-
function joinBlocks(blocks) {
|
|
75
|
-
return blocks.map((block) => typeof block === "string" ? block.trim() : "").filter((block) => block.length > 0).join("\n\n");
|
|
76
|
-
}
|
|
77
|
-
function humanizeKind(kind) {
|
|
78
|
-
return humanizeIdentifier(kind) || "Artifact";
|
|
79
|
-
}
|
|
80
|
-
function isKindValue(value) {
|
|
81
|
-
return isRecordValue(value) && typeof value[KIND_KEY] === "string" && value[KIND_KEY].trim().length > 0;
|
|
82
|
-
}
|
|
83
|
-
function scalarText(value) {
|
|
84
|
-
if (typeof value === "boolean") return value ? "Yes" : "No";
|
|
85
|
-
return String(value);
|
|
86
|
-
}
|
|
87
|
-
function cellText(value) {
|
|
88
|
-
return scalarText(value).replace(/\r?\n/g, " ").replace(/\|/g, "\\|");
|
|
89
|
-
}
|
|
90
|
-
function visibleEntries(value) {
|
|
91
|
-
return Object.entries(value).filter(
|
|
92
|
-
([key, child]) => key !== KIND_KEY && child !== null && child !== void 0 && !(typeof child === "string" && child.trim() === "") && !(Array.isArray(child) && child.length === 0)
|
|
93
|
-
);
|
|
94
|
-
}
|
|
95
|
-
function uniformTable(items) {
|
|
96
|
-
if (items.length === 0) return null;
|
|
97
|
-
let columns = null;
|
|
98
|
-
for (const item of items) {
|
|
99
|
-
if (!isRecordValue(item) || isKindValue(item)) return null;
|
|
100
|
-
const keys = Object.keys(item).filter((key) => key !== KIND_KEY);
|
|
101
|
-
if (keys.length === 0) return null;
|
|
102
|
-
if (!keys.every((key) => item[key] === null || isScalar(item[key]))) return null;
|
|
103
|
-
if (columns === null) columns = keys;
|
|
104
|
-
else if (keys.length !== columns.length || !keys.every((k, i) => k === columns[i]))
|
|
105
|
-
return null;
|
|
106
|
-
}
|
|
107
|
-
const cols = columns;
|
|
108
|
-
const head = `| ${cols.map(plainFieldLabel).join(" | ")} |`;
|
|
109
|
-
const rule = `| ${cols.map(() => "---").join(" | ")} |`;
|
|
110
|
-
const rows = items.map(
|
|
111
|
-
(item) => `| ${cols.map((key) => {
|
|
112
|
-
const cell = item[key];
|
|
113
|
-
return cell === null || cell === void 0 ? "\u2014" : cellText(cell);
|
|
114
|
-
}).join(" | ")} |`
|
|
115
|
-
);
|
|
116
|
-
return [head, rule, ...rows].join("\n");
|
|
117
|
-
}
|
|
118
|
-
function indentBlock(text, indent) {
|
|
119
|
-
return text.split("\n").map((line) => line.trim() ? indent + line : line).join("\n");
|
|
120
|
-
}
|
|
121
|
-
function listLines(value, indent, nested) {
|
|
122
|
-
if (Array.isArray(value)) {
|
|
123
|
-
const lines = [];
|
|
124
|
-
for (const item of value) {
|
|
125
|
-
if (item === null || item === void 0) continue;
|
|
126
|
-
if (isScalar(item)) lines.push(`${indent}- ${scalarText(item)}`);
|
|
127
|
-
else if (isKindValue(item)) {
|
|
128
|
-
lines.push(`${indent}-`);
|
|
129
|
-
lines.push(indentBlock(nested(item), indent + " "));
|
|
130
|
-
} else if (Array.isArray(item)) {
|
|
131
|
-
lines.push(`${indent}-`);
|
|
132
|
-
lines.push(...listLines(item, indent + " ", nested));
|
|
133
|
-
} else if (isRecordValue(item)) {
|
|
134
|
-
const fields = listLines(item, indent + " ", nested);
|
|
135
|
-
if (fields.length > 0) {
|
|
136
|
-
lines.push(`${indent}- ${fields[0].trimStart().replace(/^- /, "")}`);
|
|
137
|
-
lines.push(...fields.slice(1));
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
return lines;
|
|
142
|
-
}
|
|
143
|
-
if (isRecordValue(value)) {
|
|
144
|
-
if (isKindValue(value)) return [indentBlock(nested(value), indent)];
|
|
145
|
-
const lines = [];
|
|
146
|
-
for (const [key, child] of visibleEntries(value)) {
|
|
147
|
-
const label = `**${plainFieldLabel(key)}:**`;
|
|
148
|
-
if (isScalar(child)) lines.push(`${indent}- ${label} ${scalarText(child)}`);
|
|
149
|
-
else if (Array.isArray(child) && child.every(isScalar))
|
|
150
|
-
lines.push(`${indent}- ${label} ${child.map(scalarText).join(", ")}`);
|
|
151
|
-
else {
|
|
152
|
-
lines.push(`${indent}- ${label}`);
|
|
153
|
-
lines.push(...listLines(child, indent + " ", nested));
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
return lines;
|
|
157
|
-
}
|
|
158
|
-
return isScalar(value) ? [`${indent}- ${scalarText(value)}`] : [];
|
|
159
|
-
}
|
|
160
|
-
function headingFor(depth) {
|
|
161
|
-
return "#".repeat(Math.min(Math.max(depth, 1), 6));
|
|
162
|
-
}
|
|
163
|
-
function recordBody(value, depth, nested, skipKey) {
|
|
164
|
-
const scalars = [];
|
|
165
|
-
const sections = [];
|
|
166
|
-
for (const [key, child] of visibleEntries(value)) {
|
|
167
|
-
if (key === skipKey) continue;
|
|
168
|
-
const label = plainFieldLabel(key);
|
|
169
|
-
if (isScalar(child)) {
|
|
170
|
-
scalars.push(`- **${label}:** ${scalarText(child)}`);
|
|
171
|
-
} else if (Array.isArray(child) && child.every(isScalar)) {
|
|
172
|
-
scalars.push(`- **${label}:** ${child.map(scalarText).join(", ")}`);
|
|
173
|
-
} else if (isKindValue(child)) {
|
|
174
|
-
sections.push(joinBlocks([`${headingFor(depth + 1)} ${label}`, nested(child)]));
|
|
175
|
-
} else if (Array.isArray(child)) {
|
|
176
|
-
sections.push(
|
|
177
|
-
joinBlocks([
|
|
178
|
-
`${headingFor(depth + 1)} ${label}`,
|
|
179
|
-
uniformTable(child) ?? listLines(child, "", nested).join("\n")
|
|
180
|
-
])
|
|
181
|
-
);
|
|
182
|
-
} else if (isRecordValue(child)) {
|
|
183
|
-
sections.push(
|
|
184
|
-
joinBlocks([
|
|
185
|
-
`${headingFor(depth + 1)} ${label}`,
|
|
186
|
-
...recordBody(child, depth + 1, nested, null)
|
|
187
|
-
])
|
|
188
|
-
);
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
return [scalars.length ? scalars.join("\n") : null, ...sections].filter(
|
|
192
|
-
(block) => block !== null
|
|
193
|
-
);
|
|
194
|
-
}
|
|
195
|
-
function titleSourceKey(value, title) {
|
|
196
|
-
if (!title) return null;
|
|
197
|
-
for (const key of INSTANCE_TITLE_KEYS) {
|
|
198
|
-
const v = value[key];
|
|
199
|
-
if (typeof v === "string" && v.trim() === title) return key;
|
|
200
|
-
}
|
|
201
|
-
return null;
|
|
202
|
-
}
|
|
203
|
-
function genericKindMarkdown(kind, value, nested) {
|
|
204
|
-
const renderNested = nested ?? ((child) => genericKindMarkdown(String(child[KIND_KEY] ?? "artifact"), child));
|
|
205
|
-
const title = deriveInstanceTitle(value);
|
|
206
|
-
return joinBlocks([
|
|
207
|
-
`# ${title ?? humanizeKind(kind)}`,
|
|
208
|
-
`*${humanizeKind(kind)}*`,
|
|
209
|
-
...recordBody(value, 1, renderNested, titleSourceKey(value, title))
|
|
210
|
-
]);
|
|
211
|
-
}
|
|
212
|
-
function plainValueMarkdown(value, nested) {
|
|
213
|
-
const renderNested = nested ?? ((child) => genericKindMarkdown(String(child[KIND_KEY] ?? "artifact"), child));
|
|
214
|
-
if (value === null || value === void 0) return "";
|
|
215
|
-
if (isKindValue(value)) return renderNested(value);
|
|
216
|
-
if (Array.isArray(value)) {
|
|
217
|
-
return uniformTable(value) ?? listLines(value, "", renderNested).join("\n");
|
|
218
|
-
}
|
|
219
|
-
if (isRecordValue(value)) return joinBlocks(recordBody(value, 1, renderNested, null));
|
|
220
|
-
return isScalar(value) ? scalarText(value) : String(value);
|
|
221
|
-
}
|
|
222
|
-
export {
|
|
223
|
-
additionalDetailsSection,
|
|
224
|
-
collectExtras,
|
|
225
|
-
extrasList,
|
|
226
|
-
formatInlineValue,
|
|
227
|
-
genericKindMarkdown,
|
|
228
|
-
humanizeKind,
|
|
229
|
-
isRecordValue,
|
|
230
|
-
joinBlocks,
|
|
231
|
-
plainFieldLabel,
|
|
232
|
-
plainValueMarkdown
|
|
233
|
-
};
|
|
@@ -1,5 +0,0 @@
|
|
|
1
|
-
type KindValueToMarkdown = (value: Record<string, unknown>, fallbackKind?: string) => string;
|
|
2
|
-
/** The host's registry-backed converter (matrx-frontend: `features/canvas/export/exportArtifactMarkdown`). */
|
|
3
|
-
export declare function registerKindValueMarkdown(impl: KindValueToMarkdown | null): void;
|
|
4
|
-
export declare function kindValueToMarkdown(value: Record<string, unknown>, fallbackKind?: string): string;
|
|
5
|
-
export {};
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import { readObjectKind } from "@ai-matrx/content-ir";
|
|
2
|
-
import { reportUnregisteredHostSlot } from "../../../host/diagnostics.js";
|
|
3
|
-
import { genericKindMarkdown } from "./kind-markdown-utils.js";
|
|
4
|
-
let registered = null;
|
|
5
|
-
function registerKindValueMarkdown(impl) {
|
|
6
|
-
registered = impl;
|
|
7
|
-
}
|
|
8
|
-
function kindValueToMarkdown(value, fallbackKind = "artifact") {
|
|
9
|
-
if (registered) return registered(value, fallbackKind);
|
|
10
|
-
reportUnregisteredHostSlot("kindValueMarkdown", "kind values render through the generic readable form, not the host's kind registry");
|
|
11
|
-
const nested = (child) => kindValueToMarkdown(child);
|
|
12
|
-
return genericKindMarkdown(readObjectKind(value) ?? fallbackKind, value, nested);
|
|
13
|
-
}
|
|
14
|
-
export {
|
|
15
|
-
kindValueToMarkdown,
|
|
16
|
-
registerKindValueMarkdown
|
|
17
|
-
};
|
|
@@ -1,3 +0,0 @@
|
|
|
1
|
-
import type { CanonicalBlockIR } from "@ai-matrx/content-ir";
|
|
2
|
-
/** Mirror the server's legacy quiz adapter while keeping source aliases in IR residue. */
|
|
3
|
-
export declare function canonicalizeCompletedLegacyQuizEnvelope(envelope: CanonicalBlockIR, sourceText: string): CanonicalBlockIR;
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
function isRecord(value) {
|
|
2
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
3
|
-
}
|
|
4
|
-
function canonicalizeCompletedLegacyQuizEnvelope(envelope, sourceText) {
|
|
5
|
-
if (envelope.root.kind !== "quiz_set" || envelope.root.status !== "complete") {
|
|
6
|
-
return envelope;
|
|
7
|
-
}
|
|
8
|
-
let source;
|
|
9
|
-
try {
|
|
10
|
-
source = JSON.parse(sourceText);
|
|
11
|
-
} catch {
|
|
12
|
-
return envelope;
|
|
13
|
-
}
|
|
14
|
-
if (!isRecord(source)) return envelope;
|
|
15
|
-
const hasLegacyAliases = typeof source.quiz_title === "string" || typeof source.quizTitle === "string" || Array.isArray(source.multiple_choice) || Array.isArray(source.multipleChoice);
|
|
16
|
-
if (source.__kind !== void 0 || !hasLegacyAliases) return envelope;
|
|
17
|
-
const title = typeof source.quiz_title === "string" ? source.quiz_title : typeof source.quizTitle === "string" ? source.quizTitle : null;
|
|
18
|
-
const sourceQuestions = source.questions ?? source.multiple_choice ?? source.multipleChoice;
|
|
19
|
-
if (title === null || !Array.isArray(sourceQuestions)) return envelope;
|
|
20
|
-
const questions = sourceQuestions.flatMap((candidate) => {
|
|
21
|
-
if (!isRecord(candidate)) return [];
|
|
22
|
-
const { __kind: _legacyKind, ...legacyQuestion } = candidate;
|
|
23
|
-
const options = Array.isArray(candidate.options) ? candidate.options : [];
|
|
24
|
-
const answer = candidate.correct_answer ?? candidate.correctAnswer ?? "";
|
|
25
|
-
const correctAnswer = typeof answer === "number" && Number.isInteger(answer) && answer >= 0 && answer < options.length ? options[answer] : answer;
|
|
26
|
-
return [{
|
|
27
|
-
...legacyQuestion,
|
|
28
|
-
type: typeof candidate.type === "string" ? candidate.type : "multiple_choice",
|
|
29
|
-
question: typeof candidate.question === "string" ? candidate.question : "",
|
|
30
|
-
options,
|
|
31
|
-
correct_answer: typeof correctAnswer === "string" ? correctAnswer : String(correctAnswer),
|
|
32
|
-
explanation: typeof candidate.explanation === "string" ? candidate.explanation : null
|
|
33
|
-
}];
|
|
34
|
-
});
|
|
35
|
-
return {
|
|
36
|
-
...envelope,
|
|
37
|
-
root: {
|
|
38
|
-
...envelope.root,
|
|
39
|
-
value: { ...envelope.root.value, title, questions }
|
|
40
|
-
}
|
|
41
|
-
};
|
|
42
|
-
}
|
|
43
|
-
export {
|
|
44
|
-
canonicalizeCompletedLegacyQuizEnvelope
|
|
45
|
-
};
|