@tangle-network/agent-app 0.44.29 → 0.44.30
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/dist/assistant/index.d.ts +8 -6
- package/dist/assistant/index.js +6 -3
- package/dist/assistant/index.js.map +1 -1
- package/dist/{attachment-validation-Dvc_Livy.d.ts → attachment-validation-CNkH91Gs.d.ts} +1 -1
- package/dist/chat-react/index.d.ts +192 -0
- package/dist/chat-react/index.js +179 -0
- package/dist/chat-react/index.js.map +1 -0
- package/dist/chat-routes/index.d.ts +7 -6
- package/dist/chat-routes/index.js +12 -8
- package/dist/chat-routes/index.js.map +1 -1
- package/dist/chat-store/index.d.ts +6 -5
- package/dist/chat-store/index.js +2 -1
- package/dist/chat-store/index.js.map +1 -1
- package/dist/chunk-4OO7P3ZF.js +292 -0
- package/dist/chunk-4OO7P3ZF.js.map +1 -0
- package/dist/chunk-4PUMUTLU.js +158 -0
- package/dist/chunk-4PUMUTLU.js.map +1 -0
- package/dist/{chunk-PRKSYTMQ.js → chunk-GCH3BUAZ.js} +31 -317
- package/dist/chunk-GCH3BUAZ.js.map +1 -0
- package/dist/{chunk-BI6NKSO4.js → chunk-IYLJS6VW.js} +3 -84
- package/dist/chunk-IYLJS6VW.js.map +1 -0
- package/dist/chunk-QY4BRKRJ.js +84 -0
- package/dist/chunk-QY4BRKRJ.js.map +1 -0
- package/dist/{chunk-C3SRFZGL.js → chunk-QYAQGCHF.js} +2 -154
- package/dist/chunk-QYAQGCHF.js.map +1 -0
- package/dist/{contract-CEewO6DI.d.ts → contract-CQNvv5th.d.ts} +1 -1
- package/dist/interactions/index.d.ts +2 -2
- package/dist/{parts-fyPPdDdK.d.ts → parts-7fbe2rj8.d.ts} +4 -274
- package/dist/{queue-vRI0Qx3X.d.ts → queue-VTBA5ONX.d.ts} +1 -1
- package/dist/stream/index.d.ts +3 -3
- package/dist/{stream-normalizer-DnuqkZvw.d.ts → stream-normalizer-CnPnMaTp.d.ts} +1 -1
- package/dist/teams/index.js +5 -5
- package/dist/teams/invitations-api.js +4 -4
- package/dist/teams-react/index.js +3 -3
- package/dist/{types-DB82fktc.d.ts → types-CCeYywdS.d.ts} +1 -1
- package/dist/use-file-mentions-E6a7_cbH.d.ts +98 -0
- package/dist/web-react/index.d.ts +13 -104
- package/dist/web-react/index.js +17 -12
- package/dist/wire-DSp4LzEE.d.ts +272 -0
- package/dist/work-product/index.d.ts +3 -3
- package/dist/work-product-react/index.d.ts +1 -1
- package/package.json +6 -1
- package/dist/chunk-BI6NKSO4.js.map +0 -1
- package/dist/chunk-C3SRFZGL.js.map +0 -1
- package/dist/chunk-PRKSYTMQ.js.map +0 -1
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import {
|
|
2
|
+
persistedPartToWorkProduct
|
|
3
|
+
} from "./chunk-ZVEEWGDK.js";
|
|
4
|
+
import {
|
|
5
|
+
mentionKindForPath
|
|
6
|
+
} from "./chunk-QYAQGCHF.js";
|
|
7
|
+
import {
|
|
8
|
+
persistedPartToInteraction
|
|
9
|
+
} from "./chunk-3ZK5IJSW.js";
|
|
10
|
+
import {
|
|
11
|
+
persistedPartToPlan,
|
|
12
|
+
planToPersistedPart
|
|
13
|
+
} from "./chunk-YJMCRXQQ.js";
|
|
14
|
+
|
|
15
|
+
// src/chat-store/parts.ts
|
|
16
|
+
function toChatMessageParts(parts) {
|
|
17
|
+
const out = [];
|
|
18
|
+
for (const part of parts) {
|
|
19
|
+
const typed = toChatMessagePart(part);
|
|
20
|
+
if (typed) out.push(typed);
|
|
21
|
+
}
|
|
22
|
+
return out;
|
|
23
|
+
}
|
|
24
|
+
var str = (value) => typeof value === "string";
|
|
25
|
+
function toChatMessagePart(part) {
|
|
26
|
+
if (!part || typeof part !== "object") return null;
|
|
27
|
+
const type = part.type;
|
|
28
|
+
switch (type) {
|
|
29
|
+
case "text":
|
|
30
|
+
case "reasoning":
|
|
31
|
+
return str(part.text) ? part : null;
|
|
32
|
+
case "tool":
|
|
33
|
+
return str(part.id) && str(part.tool) && part.state && typeof part.state === "object" ? part : null;
|
|
34
|
+
case "file":
|
|
35
|
+
case "image":
|
|
36
|
+
return part;
|
|
37
|
+
case "subtask":
|
|
38
|
+
return str(part.prompt) && str(part.description) && str(part.agent) ? part : null;
|
|
39
|
+
case "step-start":
|
|
40
|
+
return { type: "step-start" };
|
|
41
|
+
case "step-finish":
|
|
42
|
+
return part;
|
|
43
|
+
case "interaction":
|
|
44
|
+
return persistedPartToInteraction(part) ? part : null;
|
|
45
|
+
case "notice":
|
|
46
|
+
return str(part.id) && str(part.noticeKind) && str(part.text) ? part : null;
|
|
47
|
+
case "plan": {
|
|
48
|
+
const plan = persistedPartToPlan(part);
|
|
49
|
+
return plan ? { ...part, ...planToPersistedPart(plan) } : null;
|
|
50
|
+
}
|
|
51
|
+
case "work_product":
|
|
52
|
+
return persistedPartToWorkProduct(part) ? part : null;
|
|
53
|
+
case "mention":
|
|
54
|
+
return isChatMentionPart(part) ? part : null;
|
|
55
|
+
case void 0:
|
|
56
|
+
return null;
|
|
57
|
+
default: {
|
|
58
|
+
const _exhaustive = type;
|
|
59
|
+
void _exhaustive;
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
function isChatToolPart(part) {
|
|
65
|
+
return part.type === "tool";
|
|
66
|
+
}
|
|
67
|
+
function isChatTextPart(part) {
|
|
68
|
+
return part.type === "text";
|
|
69
|
+
}
|
|
70
|
+
function isChatInteractionPart(part) {
|
|
71
|
+
return part.type === "interaction";
|
|
72
|
+
}
|
|
73
|
+
function isChatPlanPart(part) {
|
|
74
|
+
return part.type === "plan";
|
|
75
|
+
}
|
|
76
|
+
function isChatWorkProductPart(part) {
|
|
77
|
+
return part.type === "work_product";
|
|
78
|
+
}
|
|
79
|
+
function isChatStepFinishPart(part) {
|
|
80
|
+
return part.type === "step-finish";
|
|
81
|
+
}
|
|
82
|
+
function isChatMentionPart(part) {
|
|
83
|
+
if (!part || typeof part !== "object") return false;
|
|
84
|
+
const record = part;
|
|
85
|
+
if (record.size !== void 0 && (typeof record.size !== "number" || !Number.isFinite(record.size))) {
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
return record.type === "mention" && typeof record.path === "string" && record.path.length > 0 && typeof record.name === "string" && record.name.trim().length > 0 && (record.mentionKind === "image" || record.mentionKind === "file");
|
|
89
|
+
}
|
|
90
|
+
function mentionPartsFromMessageParts(parts) {
|
|
91
|
+
if (!parts) return [];
|
|
92
|
+
return parts.filter(isChatMentionPart);
|
|
93
|
+
}
|
|
94
|
+
function mentionInputToPart(input) {
|
|
95
|
+
const part = {
|
|
96
|
+
type: "mention",
|
|
97
|
+
mentionKind: mentionKindForPath(input.path),
|
|
98
|
+
path: input.path,
|
|
99
|
+
name: input.name
|
|
100
|
+
};
|
|
101
|
+
if (typeof input.size === "number" && Number.isFinite(input.size)) part.size = input.size;
|
|
102
|
+
return part;
|
|
103
|
+
}
|
|
104
|
+
function attachmentKindForMime(mime) {
|
|
105
|
+
return mime?.startsWith("image/") ? "image" : "file";
|
|
106
|
+
}
|
|
107
|
+
function isChatAttachmentPart(part) {
|
|
108
|
+
if (!part || typeof part !== "object") return false;
|
|
109
|
+
const record = part;
|
|
110
|
+
return (record.type === "image" || record.type === "file") && typeof record.path === "string" && record.path.length > 0;
|
|
111
|
+
}
|
|
112
|
+
function attachmentInputToPart(input) {
|
|
113
|
+
const part = { type: input.kind, path: input.path, name: input.name };
|
|
114
|
+
if (typeof input.size === "number" && Number.isFinite(input.size)) part.size = input.size;
|
|
115
|
+
if (input.mediaType) part.mediaType = input.mediaType;
|
|
116
|
+
return part;
|
|
117
|
+
}
|
|
118
|
+
function attachmentPartsFromMessageParts(parts) {
|
|
119
|
+
if (!parts) return [];
|
|
120
|
+
return parts.filter(isChatAttachmentPart);
|
|
121
|
+
}
|
|
122
|
+
var DEFAULT_ATTACHMENT_PROMPT_HEADER = "Attached files (already saved to the workspace vault \u2014 read them from these paths):";
|
|
123
|
+
var PROMPT_BLOCK_CONTROL_CHARS = /[\x00-\x1F\x7F]/g;
|
|
124
|
+
function buildAttachmentPromptBlock(atts, header = DEFAULT_ATTACHMENT_PROMPT_HEADER) {
|
|
125
|
+
if (atts.length === 0) return "";
|
|
126
|
+
const clean = (value) => value.replace(PROMPT_BLOCK_CONTROL_CHARS, " ");
|
|
127
|
+
const lines = atts.map((a) => `- ${clean(a.name)} (vault: ${clean(a.path)})`);
|
|
128
|
+
return `
|
|
129
|
+
|
|
130
|
+
${header}
|
|
131
|
+
${lines.join("\n")}`;
|
|
132
|
+
}
|
|
133
|
+
function historyContentWithAttachments(message, header) {
|
|
134
|
+
const attachmentParts = attachmentPartsFromMessageParts(message.parts);
|
|
135
|
+
if (attachmentParts.length === 0) return message.content;
|
|
136
|
+
return `${message.content}${buildAttachmentPromptBlock(attachmentParts, header)}`;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export {
|
|
140
|
+
toChatMessageParts,
|
|
141
|
+
isChatToolPart,
|
|
142
|
+
isChatTextPart,
|
|
143
|
+
isChatInteractionPart,
|
|
144
|
+
isChatPlanPart,
|
|
145
|
+
isChatWorkProductPart,
|
|
146
|
+
isChatStepFinishPart,
|
|
147
|
+
isChatMentionPart,
|
|
148
|
+
mentionPartsFromMessageParts,
|
|
149
|
+
mentionInputToPart,
|
|
150
|
+
attachmentKindForMime,
|
|
151
|
+
isChatAttachmentPart,
|
|
152
|
+
attachmentInputToPart,
|
|
153
|
+
attachmentPartsFromMessageParts,
|
|
154
|
+
DEFAULT_ATTACHMENT_PROMPT_HEADER,
|
|
155
|
+
buildAttachmentPromptBlock,
|
|
156
|
+
historyContentWithAttachments
|
|
157
|
+
};
|
|
158
|
+
//# sourceMappingURL=chunk-4PUMUTLU.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/chat-store/parts.ts"],"sourcesContent":["/**\n * The stored shape of `message.parts` — one typed vocabulary for every part a\n * product persists into a chat transcript. NOT an ad-hoc union reverse-\n * engineered from product schemas; each member is matched field-for-field to\n * its canonical source:\n *\n * - `text` / `reasoning` / `tool`: the persisted projection `/stream`'s\n * `normalizePersistedPart` produces from the harness lane's\n * `message.part.updated` events (ADC sidecar\n * `apps/sidecar/src/events/session-events.ts:56` wraps the canonical part in\n * an `{id, sessionID, messageID}` envelope; the projection strips the\n * session/message ids and keeps the per-segment part id).\n * - `file` / `image` / `step-start` / `step-finish`: the sidecar's canonical\n * `MessagePartSchema` members (ADC\n * `apps/sidecar/src/schemas/agent-schemas.ts:50-154`); `step-finish` carries\n * the harness's per-step usage receipt — tokens\n * `{total, input, output, reasoning, cache{write, read}}` + `cost` — which is\n * also the shape the message-level token/cost columns mirror.\n * - `subtask`: `@tangle-network/agent-interface`'s `SubtaskPart` (a spawned\n * sub-agent task).\n * - `interaction` / `notice`: the persisted-part codecs in\n * `/web-react`'s chat-interactions contract (`interactionToPersistedPart`,\n * `noticePart`) — type-only imports, one source of truth for their statuses\n * and field shapes.\n * - `plan`: the durable-plan projection in `/plans`, derived from the sandbox\n * SDK's authoritative plan lifecycle.\n * - `work_product`: the persisted work-product anchor card in\n * `/work-product`'s contract (`workProductToPersistedPart` /\n * `persistedPartToWorkProduct`) — SYSTEM-authored on the ready transition\n * and updated on a reviewer verdict; no prompt teaches an agent to author\n * one.\n * - `mention`: an `@`-picked reference to a file that already lives in the\n * workspace sandbox (`FileMention` in `/chat-routes`'s wire contract, plus\n * the image/file discriminant). Neither transport lane produces it — the\n * turn route persists it from the request's `mentions` field — but it is a\n * part a product persists into a transcript, so it belongs in this\n * vocabulary rather than in a parallel one.\n *\n * `@tangle-network/agent-interface` exports the canonical wire `Part` union,\n * but its `PartBase` requires the `sessionID`/`messageID` stream envelope that\n * is deliberately NOT persisted, so the stored union is defined here as the\n * envelope-free projection (a type-level coverage check against the peer's\n * `Part['type']` lives in the tests). Contribute-down candidate: if\n * agent-interface grows envelope-free persisted-part types, re-export them\n * here and delete these definitions.\n *\n * Two transport lanes serialize into this SAME stored shape:\n * - harness lane: canonical `message.part.updated` parts, merged/normalized by\n * `/stream` (`mergePersistedPart`, `finalizeAssistantParts`);\n * - router/openai-compat lane: `text_delta`/`tool_call` stream events are\n * mapped INTO canonical part events first (`/runtime`'s `toLoopEvents` +\n * `/stream`'s `normalizeToolEvent`) and then persisted identically — the\n * store never sees a router-specific shape.\n */\n\nimport type { Part as HarnessWirePart } from '@tangle-network/agent-interface'\nimport type {\n ChatInteractionField,\n ChatInteractionStatus,\n InteractionAnswers,\n InteractionPersistedPart,\n NoticeKind,\n NoticePersistedPart,\n} from '../web-react/chat-interactions'\nimport { persistedPartToInteraction } from '../interactions/contract'\nimport {\n persistedPartToWorkProduct,\n type WorkProductPersistedPart,\n} from '../work-product/types'\nimport {\n persistedPartToPlan,\n planToPersistedPart,\n type ChatPlanPersistedPart,\n} from '../plans/index'\n// `./wire` is import-free by construction, so this edge costs the store\n// nothing and keeps ONE image-extension table for the whole mention layer.\nimport {\n mentionKindForPath,\n type ChatAttachmentInput,\n type ChatAttachmentKind,\n type ChatMentionKind,\n type FileMention,\n} from '../chat-routes/wire'\n\nexport type { ChatMentionKind, ChatAttachmentKind }\n\n/** Start/end wall-clock millis, as normalized by `/stream`'s `normalizeTime`. */\nexport interface ChatPartTime {\n start?: number\n end?: number\n}\n\n/** `id` is the harness's per-segment identity; absent on legacy/router parts,\n * which collapse to a single logical text stream. Never invented client-side. */\nexport interface ChatTextPart {\n type: 'text'\n text: string\n id?: string\n}\n\n/** Define a reasoning part of a chat with text content and optional metadata fields */\nexport interface ChatReasoningPart {\n type: 'reasoning'\n text: string\n id?: string\n time?: ChatPartTime\n}\n\n/** Superset of the sidecar's status enum (`pending|running|completed|failed`)\n * and agent-interface's `ToolState` statuses; `error` is the persisted\n * terminal form `/stream`'s `normalizePersistedPart` settles on. */\nexport type ChatToolStatus = 'pending' | 'running' | 'completed' | 'error' | 'failed'\n\n/** Describe the current state and data of a chat tool including status, input, output, and metadata */\nexport interface ChatToolState {\n status: ChatToolStatus\n input?: unknown\n output?: unknown\n error?: string\n title?: string\n metadata?: Record<string, unknown>\n time?: ChatPartTime\n}\n\n/** Define a chat component representing a tool with its state and optional call identifier */\nexport interface ChatToolPart {\n type: 'tool'\n id: string\n tool: string\n callID?: string\n state: ChatToolState\n}\n\n/** Union of the sidecar's legacy (path-based) and AI-SDK (url-based) file\n * shapes; response-side every field besides `type` is optional. `name` is the\n * attachment-flavored display label an uploaded/promoted file carries (see\n * {@link ChatAttachmentPart}) — absent on a harness-emitted file part, which\n * uses `filename`. */\nexport interface ChatFilePart {\n type: 'file'\n id?: string\n filename?: string\n name?: string\n mediaType?: string\n url?: string\n path?: string\n content?: string\n}\n\n/** Define properties for an image part within a chat message including optional metadata fields */\nexport interface ChatImagePart {\n type: 'image'\n filename?: string\n name?: string\n mediaType?: string\n url?: string\n path?: string\n}\n\n/** Define a subtask part of a chat with prompt, description, agent, and optional identifier */\nexport interface ChatSubtaskPart {\n type: 'subtask'\n prompt: string\n description: string\n agent: string\n id?: string\n}\n\n/** OpenCode step-boundary marker — no renderable text; preserved so mappers\n * never coerce it into a \"[object Object]\" text part. */\nexport interface ChatStepStartPart {\n type: 'step-start'\n}\n\n/** Per-step usage receipt as the harness reports it (sidecar\n * `StepFinishPartSchema`). The message-level token/cost columns are this\n * shape flattened. */\nexport interface ChatUsageTokens {\n total?: number\n input?: number\n output?: number\n reasoning?: number\n cache?: {\n write?: number\n read?: number\n }\n}\n\n/** Define a chat step finish part indicating completion with optional reason, tokens, and cost */\nexport interface ChatStepFinishPart {\n type: 'step-finish'\n reason?: string\n tokens?: ChatUsageTokens\n cost?: number\n}\n\n/** Persisted human-in-the-loop ask — byte-matches\n * `interactionToPersistedPart` in `/web-react`'s chat-interactions contract. */\nexport interface ChatInteractionPart {\n type: 'interaction'\n id: string\n kind: string\n title: string\n body?: string\n answerSpec: { fields: ChatInteractionField[] }\n status: ChatInteractionStatus\n answers?: InteractionAnswers\n cancelReason?: string\n}\n\n/** Resolve a chat plan part by aliasing it to the persisted chat plan part type */\nexport type ChatPlanPart = ChatPlanPersistedPart\n\n/** Persisted work-product anchor card — byte-matches\n * `workProductToPersistedPart` in `/work-product`'s contract. Written by the\n * PLATFORM on the ready transition (and updated on a verdict); never\n * authored by a prompt. */\nexport type ChatWorkProductPart = WorkProductPersistedPart\n\n/** Persisted one-line transcript notice — byte-matches `noticePart` in\n * `/web-react`'s chat-interactions contract. */\nexport interface ChatNoticePart {\n type: 'notice'\n id: string\n noticeKind: NoticeKind\n text: string\n}\n\n/**\n * A file the user `@`-mentioned on this turn: a workspace-relative path into\n * the sandbox, never bytes. `type: 'mention'` is its own discriminant\n * precisely so it does NOT collide with the `file`/`image` attachment parts —\n * an attachment carries content the product uploaded, a mention points at\n * something the box already has, and a transcript renders them differently\n * (an inline pill, not an attachment card).\n *\n * `path` is the identity: mentioning one file twice in a turn folds to one\n * part. `turnId` is optional and set by products that rebuild a turn's\n * mentions on retry.\n */\nexport interface ChatMentionPart {\n type: 'mention'\n mentionKind: ChatMentionKind\n path: string\n name: string\n size?: number\n turnId?: string\n}\n\n// The \"byte-matches\" claims above, enforced at compile time: the interaction\n// contract's codec output types and the stored part types must stay mutually\n// assignable, so a codec field added on one side without the other fails here.\ntype MutuallyAssignable<A extends B, B> = A\ntype _CodecEmitsStorableInteractionPart = MutuallyAssignable<InteractionPersistedPart, ChatInteractionPart>\ntype _StoredInteractionPartFeedsCodec = MutuallyAssignable<ChatInteractionPart, InteractionPersistedPart>\ntype _CodecEmitsStorableNoticePart = MutuallyAssignable<NoticePersistedPart, ChatNoticePart>\ntype _StoredNoticePartFeedsCodec = MutuallyAssignable<ChatNoticePart, NoticePersistedPart>\ntype _CodecEmitsStorablePlanPart = MutuallyAssignable<ChatPlanPersistedPart, ChatPlanPart>\ntype _StoredPlanPartFeedsCodec = MutuallyAssignable<ChatPlanPart, ChatPlanPersistedPart>\ntype _CodecEmitsStorableWorkProductPart = MutuallyAssignable<WorkProductPersistedPart, ChatWorkProductPart>\ntype _StoredWorkProductPartFeedsCodec = MutuallyAssignable<ChatWorkProductPart, WorkProductPersistedPart>\n\n/** Represent parts of a chat message including text, reasoning, tools, files, images, subtasks, steps, interactions, notices, plans, and mentions */\nexport type ChatMessagePart =\n | ChatTextPart\n | ChatReasoningPart\n | ChatToolPart\n | ChatFilePart\n | ChatImagePart\n | ChatSubtaskPart\n | ChatStepStartPart\n | ChatStepFinishPart\n | ChatInteractionPart\n | ChatNoticePart\n | ChatPlanPart\n | ChatWorkProductPart\n | ChatMentionPart\n\n/** Every canonical harness wire-part kind must be storable — compile-time\n * guarantee that a new agent-interface part kind cannot silently fall out of\n * the persisted vocabulary. */\nexport type StorableHarnessPartKind = HarnessWirePart['type'] & ChatMessagePart['type']\n\n/**\n * The typed projection at the `/stream` → `/chat-store` boundary. The stream\n * normalizers (`normalizePersistedPart`/`mergePersistedPart`/\n * `finalizeAssistantParts`) deliberately produce untyped `JsonRecord`s — they\n * normalize wire shapes and do not own the stored vocabulary. THIS module\n * owns it, so this is where rows gain the `ChatMessagePart` type: each entry\n * is validated against its kind's required fields and narrowed, junk is\n * dropped, and — enforced by the exhaustiveness check below — no storable\n * kind can silently fall out (the step-finish/interaction trap).\n */\nexport function toChatMessageParts(parts: Array<Record<string, unknown>>): ChatMessagePart[] {\n const out: ChatMessagePart[] = []\n for (const part of parts) {\n const typed = toChatMessagePart(part)\n if (typed) out.push(typed)\n }\n return out\n}\n\nconst str = (value: unknown): value is string => typeof value === 'string'\n\nfunction toChatMessagePart(part: Record<string, unknown>): ChatMessagePart | null {\n if (!part || typeof part !== 'object') return null\n const type = part.type as ChatMessagePart['type'] | undefined\n switch (type) {\n case 'text':\n case 'reasoning':\n return str(part.text) ? (part as unknown as ChatTextPart | ChatReasoningPart) : null\n case 'tool':\n return str(part.id) && str(part.tool) && part.state && typeof part.state === 'object'\n ? (part as unknown as ChatToolPart)\n : null\n case 'file':\n case 'image':\n return part as unknown as ChatFilePart | ChatImagePart\n case 'subtask':\n return str(part.prompt) && str(part.description) && str(part.agent)\n ? (part as unknown as ChatSubtaskPart)\n : null\n case 'step-start':\n return { type: 'step-start' }\n case 'step-finish':\n return part as unknown as ChatStepFinishPart\n case 'interaction':\n return persistedPartToInteraction(part) ? (part as unknown as ChatInteractionPart) : null\n case 'notice':\n return str(part.id) && str(part.noticeKind) && str(part.text)\n ? (part as unknown as ChatNoticePart)\n : null\n case 'plan': {\n const plan = persistedPartToPlan(part)\n return plan ? ({ ...part, ...planToPersistedPart(plan) } as ChatPlanPart) : null\n }\n case 'work_product':\n // Validate via the codec but keep the original row (extra fields the\n // product persisted round-trip, matching the interaction case).\n return persistedPartToWorkProduct(part) ? (part as unknown as ChatWorkProductPart) : null\n case 'mention':\n return isChatMentionPart(part) ? part : null\n case undefined:\n return null\n default: {\n // Compile-time exhaustiveness: a new ChatMessagePart kind that is not\n // handled above makes `type` non-never here and this line fails.\n const _exhaustive: never = type\n void _exhaustive\n return null\n }\n }\n}\n\n/** Resolve whether a ChatMessagePart is specifically a ChatToolPart based on its type property */\nexport function isChatToolPart(part: ChatMessagePart): part is ChatToolPart {\n return part.type === 'tool'\n}\n\n/** Resolve whether a chat message part is a text part based on its type property */\nexport function isChatTextPart(part: ChatMessagePart): part is ChatTextPart {\n return part.type === 'text'\n}\n\n/** Resolve whether a ChatMessagePart is a ChatInteractionPart based on its type property */\nexport function isChatInteractionPart(part: ChatMessagePart): part is ChatInteractionPart {\n return part.type === 'interaction'\n}\n\n/** Resolve whether a chat message part is a persisted chat plan part */\nexport function isChatPlanPart(part: ChatMessagePart): part is ChatPlanPart {\n return part.type === 'plan'\n}\n\n/** Resolve whether a chat message part is a persisted work-product anchor */\nexport function isChatWorkProductPart(part: ChatMessagePart): part is ChatWorkProductPart {\n return part.type === 'work_product'\n}\n\n/** Determine if a chat message part represents the completion of a chat step */\nexport function isChatStepFinishPart(part: ChatMessagePart): part is ChatStepFinishPart {\n return part.type === 'step-finish'\n}\n\n/** Widened to `unknown` — unlike its siblings this guard also runs over raw\n * untyped stored rows (a transcript renderer reads `message.parts` before the\n * typed projection), which is exactly what {@link mentionPartsFromMessageParts}\n * needs. `path` and `name` carry the pill; a row missing either is unrenderable.\n *\n * Mirrors the write contract exactly (`parseFileMention` in `/chat-routes`,\n * then {@link mentionInputToPart}): a blank `name` is rejected there and so is\n * rejected here, and `size` — optional, but typed `number` once present — is\n * type-checked so `'12'` or `null` cannot ride through the guard wearing a\n * type it does not have. Negative sizes are NOT re-rejected: the wire screens\n * them, `mentionInputToPart` trusts its input, and a read guard stricter than\n * what the writer can emit would drop rows it produced itself. */\nexport function isChatMentionPart(part: unknown): part is ChatMentionPart {\n if (!part || typeof part !== 'object') return false\n const record = part as Record<string, unknown>\n if (record.size !== undefined && (typeof record.size !== 'number' || !Number.isFinite(record.size))) {\n return false\n }\n return (\n record.type === 'mention' &&\n typeof record.path === 'string' &&\n record.path.length > 0 &&\n typeof record.name === 'string' &&\n record.name.trim().length > 0 &&\n (record.mentionKind === 'image' || record.mentionKind === 'file')\n )\n}\n\n/** Every mention part on one message, in stored order. The projection a\n * transcript renderer runs before deciding which mentions the message text\n * already shows inline (see `segmentMentionContent` in `/web-react`). */\nexport function mentionPartsFromMessageParts(\n parts: ReadonlyArray<Record<string, unknown>> | ReadonlyArray<ChatMessagePart> | null | undefined,\n): ChatMentionPart[] {\n if (!parts) return []\n return (parts as ReadonlyArray<unknown>).filter(isChatMentionPart)\n}\n\n/** A validated wire mention (`parseFileMentions` in `/chat-routes`) as the\n * part the turn route persists. An absent/non-finite `size` is DROPPED rather\n * than stored as `undefined`, so a stored row never carries a key that means\n * nothing. */\nexport function mentionInputToPart(input: FileMention): ChatMentionPart {\n const part: ChatMentionPart = {\n type: 'mention',\n mentionKind: mentionKindForPath(input.path),\n path: input.path,\n name: input.name,\n }\n if (typeof input.size === 'number' && Number.isFinite(input.size)) part.size = input.size\n return part\n}\n\n// ── attachments ──────────────────────────────────────────────────────────\n//\n// An attachment is a file the product uploaded into its own store (vault /\n// object-store) ahead of the turn — content the box did not already have,\n// unlike a `@`-mention. It persists NOT as a new `ChatMessagePart` union\n// member but as an attachment-flavored `file`/`image` part: `type` is the\n// existing `image`/`file` discriminant, with `path` + `name` load-bearing.\n// That reuse is deliberate — a transcript already renders `file`/`image`\n// parts, and `toChatMessagePart` passes them through by cast (fields survive),\n// so an attachment slots into the same stored `parts` array without a parallel\n// vocabulary. These helpers own the input→part projection, the path-keyed\n// stream key, and the agent-facing prompt block, all byte-identical to the gtm\n// source they were lifted from (gtm-agent#618 adoption reproduces its rows and\n// dispatched prompt bytes through them).\n\n/** `image` for any `image/*` mime, `file` for everything else (including an\n * absent/empty mime) — the same split the composer and the persisted-part\n * discriminant both key on. */\nexport function attachmentKindForMime(mime?: string): ChatAttachmentKind {\n return mime?.startsWith('image/') ? 'image' : 'file'\n}\n\n/** Stream/transcript part key for an attachment — ONE declaration, owned by\n * `/stream`'s normalizer (whose `getPartKey` routes path-bearing file/image\n * parts through it). Re-exported here (not redefined) so the root barrel's\n * star-exports of both modules resolve to the same symbol instead of\n * colliding (TS2308 in the dts build). */\nexport { attachmentPartKey } from '../stream/stream-normalizer'\n\n/**\n * Persisted attachment part: structurally an attachment-flavored `ChatFilePart`\n * / `ChatImagePart` (`path` + `name` promoted to required) rather than a\n * separate union member — see the section note above. The index signature keeps\n * it a `Record<string, unknown>` so it drops into the same untyped `parts`\n * arrays every other stored part rides in, and lets the `type`-driven\n * constructors below build it without union-discriminant gymnastics.\n */\nexport interface ChatAttachmentPart {\n type: ChatAttachmentKind\n path: string\n name: string\n size?: number\n mediaType?: string\n turnId?: string\n [key: string]: unknown\n}\n\n/** True for any `image`/`file` part carrying a non-empty string `path`.\n * DELIBERATELY also matches a sidecar path-bearing file part (a mention-style\n * `file` part with only `path`) — the guard is a structural attachment shape,\n * not proof of provenance; a caller that needs to exclude those filters\n * upstream. The `path.length > 0` check diverges from gtm's original guard\n * (which only checked `typeof`), mirroring sibling {@link isChatMentionPart}:\n * read-side robustness against a stored `{type:'file', path:''}` row — which\n * would otherwise render a malformed `(vault: )` pointer line — justifies the\n * small divergence from byte-for-byte gtm parity here; no legitimate write\n * path ever produces an empty `path`. */\nexport function isChatAttachmentPart(part: unknown): part is ChatAttachmentPart {\n if (!part || typeof part !== 'object') return false\n const record = part as Record<string, unknown>\n return (\n (record.type === 'image' || record.type === 'file') &&\n typeof record.path === 'string' &&\n record.path.length > 0\n )\n}\n\n/** Drop absent/empty optional fields rather than persisting `undefined`/`''`\n * — keeps stored parts minimal. */\nexport function attachmentInputToPart(input: ChatAttachmentInput): ChatAttachmentPart {\n const part: ChatAttachmentPart = { type: input.kind, path: input.path, name: input.name }\n if (typeof input.size === 'number' && Number.isFinite(input.size)) part.size = input.size\n if (input.mediaType) part.mediaType = input.mediaType\n return part\n}\n\n/** Every attachment part on one message's stored `parts`, in stored order. */\nexport function attachmentPartsFromMessageParts(\n parts: ReadonlyArray<Record<string, unknown>> | null | undefined,\n): ChatAttachmentPart[] {\n if (!parts) return []\n return parts.filter(isChatAttachmentPart)\n}\n\n/** gtm's exact default header for {@link buildAttachmentPromptBlock} — kept\n * byte-identical because it feeds dispatched prompts (gtm-agent#618\n * reproduces the prompt bytes through this module). Override only with intent. */\nexport const DEFAULT_ATTACHMENT_PROMPT_HEADER =\n 'Attached files (already saved to the workspace vault — read them from these paths):'\n\n/** Same C0-control/DEL sweep as `/chat-routes`'s `resolve-attachments.ts`\n * (kept as a separate literal rather than a shared import — this module is\n * the read side and must stay usable over PERSISTED rows the wire validator\n * never saw). Replaced with a single space, not stripped or collapsed: this\n * is defense-in-depth for legacy/corrupt stored rows that bypassed wire\n * validation, not the primary control — a name/path a `resolveChatAttachments`\n * call validated today never contains one, so this is a no-op on legitimate\n * input and gtm byte-compat holds. */\nconst PROMPT_BLOCK_CONTROL_CHARS = /[\\x00-\\x1F\\x7F]/g\n\n/**\n * The agent-facing pointer block appended to the dispatched prompt — never\n * persisted in `message.content`. Empty array → `''` so callers can append\n * unconditionally. The sole producer of this text: both the current turn's\n * dispatch and history projection ({@link historyContentWithAttachments}) build\n * it from here, so the two can never drift. `header` overrides the first line\n * ({@link DEFAULT_ATTACHMENT_PROMPT_HEADER}); the per-file line format is fixed.\n *\n * Every interpolated `name`/`path` is swept for control characters first\n * (newline/carriage-return/tab included) and each one replaced with a single\n * space — a single-line-per-file guarantee. Without it, a control character\n * riding through on a PERSISTED row (`historyContentWithAttachments` reads\n * whatever is already stored; a legacy or hand-edited row was never re-checked\n * against `resolveChatAttachments`) could inject extra lines — fabricated\n * pointer entries or instructions — into a prompt the agent trusts verbatim.\n * The wire-side validator is the primary control; this is the backstop for\n * rows it never saw.\n */\nexport function buildAttachmentPromptBlock(\n atts: ReadonlyArray<Pick<ChatAttachmentPart, 'name' | 'path'>>,\n header: string = DEFAULT_ATTACHMENT_PROMPT_HEADER,\n): string {\n if (atts.length === 0) return ''\n const clean = (value: string) => value.replace(PROMPT_BLOCK_CONTROL_CHARS, ' ')\n const lines = atts.map((a) => `- ${clean(a.name)} (vault: ${clean(a.path)})`)\n return `\\n\\n${header}\\n${lines.join('\\n')}`\n}\n\n/**\n * History projection for a persisted message: `content` unchanged when it\n * carries no attachment parts, else the block is appended once. The\n * no-double-annotation guarantee holds under the invariant that the block is\n * never persisted INTO `message.content` — it is appended only at read time,\n * by this function and the current turn's dispatch, both sourcing from\n * `attachmentPartsFromMessageParts` — never by content inspection. This\n * function does not (and cannot) detect an already-embedded block by scanning\n * `content` for its text: a message whose stored `content` happens to already\n * contain the block's bytes, alongside attachment parts, still gets the block\n * appended a second time. See the pinned test for that boundary.\n */\nexport function historyContentWithAttachments(\n message: {\n content: string\n parts?: ReadonlyArray<Record<string, unknown>> | null\n },\n header?: string,\n): string {\n const attachmentParts = attachmentPartsFromMessageParts(message.parts)\n if (attachmentParts.length === 0) return message.content\n return `${message.content}${buildAttachmentPromptBlock(attachmentParts, header)}`\n}\n"],"mappings":"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","names":[]}
|