@kitn.ai/ui 0.22.0 → 0.22.1
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/mcp.es.js +88 -10
- package/package.json +3 -1
- package/src/agent-tooling/mcp/tools/scaffold.ts +70 -10
- package/src/wire/encode.ts +5 -0
package/dist/mcp.es.js
CHANGED
|
@@ -737,14 +737,14 @@ Call component_reference with no name (or name: "list") to list all ${all.length
|
|
|
737
737
|
lines.push(`- **${slot.name}**${desc ? ` — ${desc}` : ""}`);
|
|
738
738
|
}
|
|
739
739
|
}
|
|
740
|
-
const
|
|
741
|
-
if (
|
|
740
|
+
const parts2 = el.cssParts ?? [];
|
|
741
|
+
if (parts2.length > 0) {
|
|
742
742
|
lines.push(
|
|
743
743
|
"",
|
|
744
744
|
"### Styleable parts (`::part`)",
|
|
745
745
|
"Restyle these from outside the Shadow DOM via `" + tag + "::part(name) { … }`."
|
|
746
746
|
);
|
|
747
|
-
for (const part of
|
|
747
|
+
for (const part of parts2) {
|
|
748
748
|
const desc = part.description?.trim() ?? "";
|
|
749
749
|
lines.push(`- **${part.name}**${desc ? ` — ${desc}` : ""}`);
|
|
750
750
|
if (part.recipe) {
|
|
@@ -2594,6 +2594,83 @@ function listIntegrations() {
|
|
|
2594
2594
|
function listArchetypes() {
|
|
2595
2595
|
return archetypes;
|
|
2596
2596
|
}
|
|
2597
|
+
const ENCODABLE = [
|
|
2598
|
+
{ pattern: "image/jpeg", kind: "image" },
|
|
2599
|
+
{ pattern: "image/png", kind: "image" },
|
|
2600
|
+
{ pattern: "image/gif", kind: "image" },
|
|
2601
|
+
{ pattern: "image/webp", kind: "image" },
|
|
2602
|
+
{ pattern: "application/pdf", kind: "document" },
|
|
2603
|
+
{ pattern: "text/*", kind: "text" },
|
|
2604
|
+
{ pattern: "application/json", kind: "text" },
|
|
2605
|
+
{ pattern: "application/xml", kind: "text" },
|
|
2606
|
+
{ pattern: "application/x-yaml", kind: "text" },
|
|
2607
|
+
{ pattern: "application/yaml", kind: "text" }
|
|
2608
|
+
];
|
|
2609
|
+
const UNNAMED_TEXT_MEDIA_TYPE = "text/plain";
|
|
2610
|
+
const NAMES_NOTHING = ["", "application/octet-stream"];
|
|
2611
|
+
const normalize = (value) => value.trim().toLowerCase();
|
|
2612
|
+
const namesNothing = (mediaType) => NAMES_NOTHING.includes(mediaType === void 0 ? "" : normalize(mediaType));
|
|
2613
|
+
const assertMediaType = (entry) => {
|
|
2614
|
+
if (!entry.startsWith(".")) return entry;
|
|
2615
|
+
throw new Error(
|
|
2616
|
+
`\`accept\` entry "${entry}" is a file extension, and this kit takes media types only. HTML's own accept attribute does take extensions, which is exactly why this throws rather than quietly matching nothing. Use a media type instead -- "text/*" covers source files, "image/*" images, "application/pdf" PDFs, and \`encodableMediaTypes()\` is the full set. You lose nothing by dropping the extension: a file the browser cannot name is decided by decoding its bytes, which is stronger than matching its name.`
|
|
2617
|
+
);
|
|
2618
|
+
};
|
|
2619
|
+
const splitFilter = (filter) => (typeof filter === "string" ? filter.split(",") : filter).map(normalize).filter((v2) => v2 !== "").map(assertMediaType);
|
|
2620
|
+
const parts = (pattern) => {
|
|
2621
|
+
const slash = pattern.indexOf("/");
|
|
2622
|
+
return slash === -1 ? [pattern, ""] : [pattern.slice(0, slash), pattern.slice(slash + 1)];
|
|
2623
|
+
};
|
|
2624
|
+
const matches = (pattern, mediaType) => {
|
|
2625
|
+
const [pType, pSub] = parts(pattern);
|
|
2626
|
+
const [mType, mSub] = parts(mediaType);
|
|
2627
|
+
if (pType !== "*" && pType !== mType) return false;
|
|
2628
|
+
return pSub === "*" || pSub === mSub;
|
|
2629
|
+
};
|
|
2630
|
+
const intersect = (capability, filter) => {
|
|
2631
|
+
if (matches(capability, filter)) return filter;
|
|
2632
|
+
if (matches(filter, capability)) return capability;
|
|
2633
|
+
return void 0;
|
|
2634
|
+
};
|
|
2635
|
+
function encodableMediaTypes() {
|
|
2636
|
+
return ENCODABLE.map((entry) => entry.pattern);
|
|
2637
|
+
}
|
|
2638
|
+
function resolveMediaPolicy(options = {}) {
|
|
2639
|
+
const filter = options.accept === void 0 ? void 0 : splitFilter(options.accept);
|
|
2640
|
+
const effective = [];
|
|
2641
|
+
for (const capability of ENCODABLE) {
|
|
2642
|
+
if (filter === void 0) {
|
|
2643
|
+
effective.push({ ...capability });
|
|
2644
|
+
continue;
|
|
2645
|
+
}
|
|
2646
|
+
for (const wanted of filter) {
|
|
2647
|
+
const overlap = intersect(capability.pattern, wanted);
|
|
2648
|
+
if (overlap !== void 0 && !effective.some((e) => e.pattern === overlap)) {
|
|
2649
|
+
effective.push({ pattern: overlap, kind: capability.kind });
|
|
2650
|
+
}
|
|
2651
|
+
}
|
|
2652
|
+
}
|
|
2653
|
+
return {
|
|
2654
|
+
types: effective.map((e) => e.pattern),
|
|
2655
|
+
accept: effective.map((e) => e.pattern).join(","),
|
|
2656
|
+
decide(mediaType) {
|
|
2657
|
+
const type = mediaType === void 0 ? "" : normalize(mediaType);
|
|
2658
|
+
if (namesNothing(type)) {
|
|
2659
|
+
return effective.some((e) => matches(e.pattern, UNNAMED_TEXT_MEDIA_TYPE)) ? { status: "undetermined" } : (
|
|
2660
|
+
// `unsupported` rather than `filtered`, deliberately: `filtered`
|
|
2661
|
+
// claims the kit COULD have encoded this file, and nobody has
|
|
2662
|
+
// established what it is. Unread and unnamed, it is not a text
|
|
2663
|
+
// file the filter excluded -- it is a file with no known type.
|
|
2664
|
+
{ status: "unsupported" }
|
|
2665
|
+
);
|
|
2666
|
+
}
|
|
2667
|
+
const hit = effective.find((e) => matches(e.pattern, type));
|
|
2668
|
+
if (hit) return { status: "allowed", kind: hit.kind };
|
|
2669
|
+
return ENCODABLE.some((e) => matches(e.pattern, type)) ? { status: "filtered" } : { status: "unsupported" };
|
|
2670
|
+
}
|
|
2671
|
+
};
|
|
2672
|
+
}
|
|
2673
|
+
resolveMediaPolicy();
|
|
2597
2674
|
const text = (s) => ({
|
|
2598
2675
|
content: [{ type: "text", text: s }]
|
|
2599
2676
|
});
|
|
@@ -3015,6 +3092,7 @@ const ATTACHMENT_TAGS = /* @__PURE__ */ new Set(["kai-file-upload", "kai-attachm
|
|
|
3015
3092
|
function hasAttachments(components) {
|
|
3016
3093
|
return components.includes("kai-file-upload") && components.includes("kai-attachments");
|
|
3017
3094
|
}
|
|
3095
|
+
const ATTACHMENT_ACCEPT = encodableMediaTypes().join(",");
|
|
3018
3096
|
const ATTACHMENT_WIRE_NOTE = [
|
|
3019
3097
|
"// The staged files ride along on the message as `file` parts, so they RENDER",
|
|
3020
3098
|
"// in the thread AND reach the model: toOpenAIMessages / toAnthropicMessages",
|
|
@@ -3119,7 +3197,7 @@ function componentTags(components, chatFill) {
|
|
|
3119
3197
|
` <!-- Drop files here to stage them for the NEXT message. src/main.ts wires`,
|
|
3120
3198
|
` kai-files-added -> the staged list -> kai-attachments' items property. -->`,
|
|
3121
3199
|
` <div style="flex: 0 0 auto; display: flex; flex-direction: column; gap: 0.5rem; padding: 0.75rem;">`,
|
|
3122
|
-
` <kai-file-upload id="upload" accept="
|
|
3200
|
+
` <kai-file-upload id="upload" accept="${ATTACHMENT_ACCEPT}"></kai-file-upload>`,
|
|
3123
3201
|
` <!-- items is a JS PROPERTY (arrays can't be attributes); 'removable' fires kai-remove. -->`,
|
|
3124
3202
|
` <kai-attachments id="attachments" variant="inline" removable></kai-attachments>`,
|
|
3125
3203
|
` </div>`
|
|
@@ -3382,7 +3460,7 @@ function renderJsx(components, ctx, framework) {
|
|
|
3382
3460
|
const attachmentJsx = attachments ? [
|
|
3383
3461
|
` {/* Drop files here to stage them for the NEXT message. */}`,
|
|
3384
3462
|
` <div style={{ flex: '0 0 auto', display: 'flex', flexDirection: 'column', gap: '0.5rem', padding: '0.75rem' }}>`,
|
|
3385
|
-
` <FileUpload accept="
|
|
3463
|
+
` <FileUpload accept="${ATTACHMENT_ACCEPT}" onFilesAdded={onFilesAdded} />`,
|
|
3386
3464
|
` {/* items is an ARRAY, so the wrapper sets it as a DOM property, never an attribute. */}`,
|
|
3387
3465
|
` <Attachments items={staged} variant="inline" removable onRemove={onRemoveAttachment} />`,
|
|
3388
3466
|
` </div>`
|
|
@@ -3659,7 +3737,7 @@ function renderVue(components, ctx) {
|
|
|
3659
3737
|
const attachmentTemplate = attachments ? [
|
|
3660
3738
|
` <!-- Drop files here to stage them for the NEXT message. -->`,
|
|
3661
3739
|
` <div style="flex: 0 0 auto; display: flex; flex-direction: column; gap: 0.5rem; padding: 0.75rem;">`,
|
|
3662
|
-
` <kai-file-upload accept="
|
|
3740
|
+
` <kai-file-upload accept="${ATTACHMENT_ACCEPT}" @kai-files-added="onFilesAdded" />`,
|
|
3663
3741
|
` <kai-attachments :items.prop="staged" variant="inline" removable @kai-remove="onRemoveAttachment" />`,
|
|
3664
3742
|
` </div>`
|
|
3665
3743
|
].join("\n") : "";
|
|
@@ -3914,7 +3992,7 @@ function renderSvelte(components, ctx) {
|
|
|
3914
3992
|
const attachmentMarkup = attachments ? [
|
|
3915
3993
|
` <!-- Drop files here to stage them for the NEXT message. -->`,
|
|
3916
3994
|
` <div style="flex: 0 0 auto; display: flex; flex-direction: column; gap: 0.5rem; padding: 0.75rem;">`,
|
|
3917
|
-
` <kai-file-upload accept="
|
|
3995
|
+
` <kai-file-upload accept="${ATTACHMENT_ACCEPT}" onkai-files-added={onFilesAdded}></kai-file-upload>`,
|
|
3918
3996
|
` <kai-attachments bind:this={attachmentsEl} variant="inline" removable onkai-remove={onRemoveAttachment}></kai-attachments>`,
|
|
3919
3997
|
` </div>`
|
|
3920
3998
|
] : [];
|
|
@@ -4033,7 +4111,7 @@ function renderTanstackStart(components, ctx) {
|
|
|
4033
4111
|
const attachmentJsx = attachments ? [
|
|
4034
4112
|
` {/* Drop files here to stage them for the NEXT message. */}`,
|
|
4035
4113
|
` <div style={{ flex: '0 0 auto', display: 'flex', flexDirection: 'column', gap: '0.5rem', padding: '0.75rem' }}>`,
|
|
4036
|
-
` <FileUpload accept="
|
|
4114
|
+
` <FileUpload accept="${ATTACHMENT_ACCEPT}" onFilesAdded={onFilesAdded} />`,
|
|
4037
4115
|
` {/* items is an ARRAY, so the wrapper sets it as a DOM property, never an attribute. */}`,
|
|
4038
4116
|
` <Attachments items={staged} variant="inline" removable onRemove={onRemoveAttachment} />`,
|
|
4039
4117
|
` </div>`
|
|
@@ -4235,7 +4313,7 @@ function renderAngular(components, ctx) {
|
|
|
4235
4313
|
const attachmentTemplate = attachments ? [
|
|
4236
4314
|
` <!-- Drop files here to stage them for the NEXT message. -->`,
|
|
4237
4315
|
` <div style="flex: 0 0 auto; display: flex; flex-direction: column; gap: 0.5rem; padding: 0.75rem;">`,
|
|
4238
|
-
` <kai-file-upload accept="
|
|
4316
|
+
` <kai-file-upload accept="${ATTACHMENT_ACCEPT}" (kai-files-added)="onFilesAdded($event)"></kai-file-upload>`,
|
|
4239
4317
|
` <kai-attachments [items]="staged()" variant="inline" removable (kai-remove)="onRemoveAttachment($event)"></kai-attachments>`,
|
|
4240
4318
|
` </div>`
|
|
4241
4319
|
] : [];
|
|
@@ -4598,7 +4676,7 @@ function renderSolid(components, ctx) {
|
|
|
4598
4676
|
` {/* Read every file BEFORE appending: a per-file append would order`,
|
|
4599
4677
|
` the list by whichever finished reading first. */}`,
|
|
4600
4678
|
` <FileUpload`,
|
|
4601
|
-
` accept="
|
|
4679
|
+
` accept="${ATTACHMENT_ACCEPT}"`,
|
|
4602
4680
|
` onFilesAdded={async (files) => setStaged([...staged(), ...(await Promise.all(files.map(toAttachment)))])}`,
|
|
4603
4681
|
` >`,
|
|
4604
4682
|
` <FileUploadTrigger class="border-border text-muted-foreground w-full rounded-xl border border-dashed px-4 py-3 text-center text-sm">`,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kitn.ai/ui",
|
|
3
|
-
"version": "0.22.
|
|
3
|
+
"version": "0.22.1",
|
|
4
4
|
"nx": {
|
|
5
5
|
"name": "ui",
|
|
6
6
|
"targets": {
|
|
@@ -178,6 +178,8 @@
|
|
|
178
178
|
"verify:ssr:render": "node scripts/verify-ssr-render.mjs",
|
|
179
179
|
"verify:pack": "node scripts/verify-pack-weight.mjs",
|
|
180
180
|
"verify:starters": "node scripts/verify-starters.mjs",
|
|
181
|
+
"lint:attachment-object-urls": "node scripts/lint-attachment-object-urls.mjs --self-test && node scripts/lint-attachment-object-urls.mjs",
|
|
182
|
+
"lint:silent-drops": "node scripts/lint-silent-drops.mjs --self-test && node scripts/lint-silent-drops.mjs",
|
|
181
183
|
"test": "vitest run",
|
|
182
184
|
"test:react": "vitest run --config vitest.react.config.ts",
|
|
183
185
|
"test:storybook": "vitest run --project=storybook",
|
|
@@ -8,6 +8,12 @@ import {
|
|
|
8
8
|
listArchetypes,
|
|
9
9
|
listIntegrations,
|
|
10
10
|
} from '../../registry';
|
|
11
|
+
// The kit's media-type declaration, read rather than restated. This is the one
|
|
12
|
+
// import in agent-tooling that reaches outside itself, and the module it reaches
|
|
13
|
+
// for is the reason: `wire/media-types.ts` is pure (no I/O, no DOM, no solid-js),
|
|
14
|
+
// so the Node MCP pass typechecks and bundles it unchanged. See
|
|
15
|
+
// `ATTACHMENT_ACCEPT`.
|
|
16
|
+
import { encodableMediaTypes } from '../../../wire/media-types';
|
|
11
17
|
|
|
12
18
|
/**
|
|
13
19
|
* scaffold — the keystone tool. Composes a working chat surface from four axes:
|
|
@@ -1081,6 +1087,40 @@ function hasAttachments(components: readonly string[]): boolean {
|
|
|
1081
1087
|
return components.includes('kai-file-upload') && components.includes('kai-attachments');
|
|
1082
1088
|
}
|
|
1083
1089
|
|
|
1090
|
+
/**
|
|
1091
|
+
* ★ THE EMITTED `accept`, DERIVED. The seven renderers below interpolate this;
|
|
1092
|
+
* none of them writes a media type.
|
|
1093
|
+
*
|
|
1094
|
+
* It used to be the literal `image/*,application/pdf` in all seven, which was
|
|
1095
|
+
* right when only images and PDFs could be encoded and silently stopped being
|
|
1096
|
+
* right when #190 made `text/*`, `application/json`, `application/xml` and YAML
|
|
1097
|
+
* encodable. The list did not become wrong in this file — it became wrong in
|
|
1098
|
+
* every app scaffolded from it, which is the worst place for a copy to live and
|
|
1099
|
+
* exactly what `wire/media-types.ts` exists to prevent. An `accept` attribute is
|
|
1100
|
+
* a harder case than the prose in `ATTACHMENT_WIRE_NOTE` only in that a comment
|
|
1101
|
+
* can point at a function and an attribute has to CONTAIN the answer; the fix is
|
|
1102
|
+
* the same either way, which is to read the declaration rather than restate it.
|
|
1103
|
+
*
|
|
1104
|
+
* WHY EMIT IT AT ALL, RATHER THAN NOTHING. An absent filter means the full
|
|
1105
|
+
* capability set to `resolveMediaPolicy`, so omitting looks equivalent and is
|
|
1106
|
+
* not, because the tags emitted here never call it. `<kai-file-upload>` (and the
|
|
1107
|
+
* Solid `<FileUpload>`) pass `accept` straight to `<input type="file">` and do no
|
|
1108
|
+
* JS filtering at all — unlike `<kai-chat accept>`, which resolves the policy in
|
|
1109
|
+
* `default-input.tsx`. So on THESE tags an absent attribute is not the capability
|
|
1110
|
+
* set, it is wider than the capability set: the dialog offers `.zip`, the emitted
|
|
1111
|
+
* `toAttachment` stages it, and the encoder throws on a file the picker
|
|
1112
|
+
* volunteered. Deriving the attribute is the only form that tracks the
|
|
1113
|
+
* declaration; omitting it is a third behaviour that matches neither layer.
|
|
1114
|
+
*
|
|
1115
|
+
* IT IS A STEER, NOT A GATE, and nothing here should be read as claiming
|
|
1116
|
+
* otherwise. `accept` filters the OS dialog, which always offers an "All Files"
|
|
1117
|
+
* escape, and it does not apply to drag-and-drop — `file-upload.tsx` hands
|
|
1118
|
+
* `dataTransfer.files` straight to `onFilesAdded`. A scaffolded app that wants a
|
|
1119
|
+
* filter that HOLDS calls `resolveMediaPolicy().decide()` on what it staged; the
|
|
1120
|
+
* emitted note points at it.
|
|
1121
|
+
*/
|
|
1122
|
+
const ATTACHMENT_ACCEPT = encodableMediaTypes().join(',');
|
|
1123
|
+
|
|
1084
1124
|
/**
|
|
1085
1125
|
* What the scaffolder INTENDS to emit for one components list, so a guard can
|
|
1086
1126
|
* check the seven renderers against the decision instead of restating it.
|
|
@@ -1161,15 +1201,35 @@ export const ATTACHMENT_WIRE_NOTE = [
|
|
|
1161
1201
|
*
|
|
1162
1202
|
* `<kai-file-upload>` hands over `File` objects and `<kai-attachments>` renders
|
|
1163
1203
|
* `AttachmentData`, so something has to bridge them and it may as well be code
|
|
1164
|
-
* the consumer can edit.
|
|
1165
|
-
*
|
|
1166
|
-
*
|
|
1204
|
+
* the consumer can edit.
|
|
1205
|
+
*
|
|
1206
|
+
* It reads the file with `FileReader.readAsDataURL` and stages a `data:` URI —
|
|
1207
|
+
* the same call `readAsDataUrl` in `elements/default-input.tsx` makes, for the
|
|
1208
|
+
* same reason. `URL.createObjectURL` would draw an identical thumbnail and be
|
|
1209
|
+
* meaningless to anything downstream: an object URL resolves only inside the tab
|
|
1210
|
+
* that minted it, so `toOpenAIMessages` / `toAnthropicMessages` refuse it rather
|
|
1211
|
+
* than send a provider an address it cannot fetch. A `data:` URI previews the
|
|
1212
|
+
* same and is the one form both APIs actually take. Reading is async, which is
|
|
1213
|
+
* the only reason the emitted function is.
|
|
1214
|
+
*
|
|
1215
|
+
* WHICH files survive that encoding is deliberately not stated here, and the
|
|
1216
|
+
* emitted code does not state it either: `encodableMediaTypes()` is the set, and
|
|
1217
|
+
* is public precisely so that nothing has to keep a second copy of it (the
|
|
1218
|
+
* reasoning is on `ATTACHMENT_WIRE_NOTE` above).
|
|
1167
1219
|
*
|
|
1168
1220
|
* The parameter type is derived from the property it feeds
|
|
1169
1221
|
* (`KaiAttachmentsElement['items']` / the wrapper's `items` prop) at each call
|
|
1170
1222
|
* site rather than importing `AttachmentData`, for the reason the html target
|
|
1171
1223
|
* already derives its message type from `KaiChatElement['messages']`: a type
|
|
1172
1224
|
* read off the assignment target cannot drift out of step with it.
|
|
1225
|
+
*
|
|
1226
|
+
* This comment taught the reverse until now, and approvingly — it described the
|
|
1227
|
+
* object URL as what made a preview a real thumbnail and called leaving it
|
|
1228
|
+
* unrevoked a deliberate choice, having gone untouched since #185 while #186
|
|
1229
|
+
* rewrote the function under it. That is the second time in this file the code
|
|
1230
|
+
* was corrected and the prose above it was not; the paragraph on
|
|
1231
|
+
* `ATTACHMENT_WIRE_NOTE` records the first. Neither claim had anything to
|
|
1232
|
+
* disagree with, which is the whole hazard of explaining emitted code in prose.
|
|
1173
1233
|
*/
|
|
1174
1234
|
function fileToAttachmentLines(pad: string, typeName: string): string[] {
|
|
1175
1235
|
return [
|
|
@@ -1404,7 +1464,7 @@ function componentTags(components: readonly string[], chatFill: string): string
|
|
|
1404
1464
|
` <!-- Drop files here to stage them for the NEXT message. src/main.ts wires`,
|
|
1405
1465
|
` kai-files-added -> the staged list -> kai-attachments' items property. -->`,
|
|
1406
1466
|
` <div style="flex: 0 0 auto; display: flex; flex-direction: column; gap: 0.5rem; padding: 0.75rem;">`,
|
|
1407
|
-
` <kai-file-upload id="upload" accept="
|
|
1467
|
+
` <kai-file-upload id="upload" accept="${ATTACHMENT_ACCEPT}"></kai-file-upload>`,
|
|
1408
1468
|
` <!-- items is a JS PROPERTY (arrays can't be attributes); 'removable' fires kai-remove. -->`,
|
|
1409
1469
|
` <kai-attachments id="attachments" variant="inline" removable></kai-attachments>`,
|
|
1410
1470
|
` </div>`,
|
|
@@ -1815,7 +1875,7 @@ function renderJsx(components: readonly string[], ctx: RenderCtx, framework: str
|
|
|
1815
1875
|
? [
|
|
1816
1876
|
` {/* Drop files here to stage them for the NEXT message. */}`,
|
|
1817
1877
|
` <div style={{ flex: '0 0 auto', display: 'flex', flexDirection: 'column', gap: '0.5rem', padding: '0.75rem' }}>`,
|
|
1818
|
-
` <FileUpload accept="
|
|
1878
|
+
` <FileUpload accept="${ATTACHMENT_ACCEPT}" onFilesAdded={onFilesAdded} />`,
|
|
1819
1879
|
` {/* items is an ARRAY, so the wrapper sets it as a DOM property, never an attribute. */}`,
|
|
1820
1880
|
` <Attachments items={staged} variant="inline" removable onRemove={onRemoveAttachment} />`,
|
|
1821
1881
|
` </div>`,
|
|
@@ -2176,7 +2236,7 @@ function renderVue(components: readonly string[], ctx: RenderCtx): string {
|
|
|
2176
2236
|
? [
|
|
2177
2237
|
` <!-- Drop files here to stage them for the NEXT message. -->`,
|
|
2178
2238
|
` <div style="flex: 0 0 auto; display: flex; flex-direction: column; gap: 0.5rem; padding: 0.75rem;">`,
|
|
2179
|
-
` <kai-file-upload accept="
|
|
2239
|
+
` <kai-file-upload accept="${ATTACHMENT_ACCEPT}" @kai-files-added="onFilesAdded" />`,
|
|
2180
2240
|
` <kai-attachments :items.prop="staged" variant="inline" removable @kai-remove="onRemoveAttachment" />`,
|
|
2181
2241
|
` </div>`,
|
|
2182
2242
|
].join('\n')
|
|
@@ -2533,7 +2593,7 @@ function renderSvelte(components: readonly string[], ctx: RenderCtx): string {
|
|
|
2533
2593
|
? [
|
|
2534
2594
|
` <!-- Drop files here to stage them for the NEXT message. -->`,
|
|
2535
2595
|
` <div style="flex: 0 0 auto; display: flex; flex-direction: column; gap: 0.5rem; padding: 0.75rem;">`,
|
|
2536
|
-
` <kai-file-upload accept="
|
|
2596
|
+
` <kai-file-upload accept="${ATTACHMENT_ACCEPT}" onkai-files-added={onFilesAdded}></kai-file-upload>`,
|
|
2537
2597
|
` <kai-attachments bind:this={attachmentsEl} variant="inline" removable onkai-remove={onRemoveAttachment}></kai-attachments>`,
|
|
2538
2598
|
` </div>`,
|
|
2539
2599
|
]
|
|
@@ -2714,7 +2774,7 @@ function renderTanstackStart(components: readonly string[], ctx: RenderCtx): str
|
|
|
2714
2774
|
? [
|
|
2715
2775
|
` {/* Drop files here to stage them for the NEXT message. */}`,
|
|
2716
2776
|
` <div style={{ flex: '0 0 auto', display: 'flex', flexDirection: 'column', gap: '0.5rem', padding: '0.75rem' }}>`,
|
|
2717
|
-
` <FileUpload accept="
|
|
2777
|
+
` <FileUpload accept="${ATTACHMENT_ACCEPT}" onFilesAdded={onFilesAdded} />`,
|
|
2718
2778
|
` {/* items is an ARRAY, so the wrapper sets it as a DOM property, never an attribute. */}`,
|
|
2719
2779
|
` <Attachments items={staged} variant="inline" removable onRemove={onRemoveAttachment} />`,
|
|
2720
2780
|
` </div>`,
|
|
@@ -2989,7 +3049,7 @@ function renderAngular(components: readonly string[], ctx: RenderCtx): string {
|
|
|
2989
3049
|
? [
|
|
2990
3050
|
` <!-- Drop files here to stage them for the NEXT message. -->`,
|
|
2991
3051
|
` <div style="flex: 0 0 auto; display: flex; flex-direction: column; gap: 0.5rem; padding: 0.75rem;">`,
|
|
2992
|
-
` <kai-file-upload accept="
|
|
3052
|
+
` <kai-file-upload accept="${ATTACHMENT_ACCEPT}" (kai-files-added)="onFilesAdded($event)"></kai-file-upload>`,
|
|
2993
3053
|
` <kai-attachments [items]="staged()" variant="inline" removable (kai-remove)="onRemoveAttachment($event)"></kai-attachments>`,
|
|
2994
3054
|
` </div>`,
|
|
2995
3055
|
]
|
|
@@ -3498,7 +3558,7 @@ function renderSolid(components: readonly string[], ctx: RenderCtx): string {
|
|
|
3498
3558
|
` {/* Read every file BEFORE appending: a per-file append would order`,
|
|
3499
3559
|
` the list by whichever finished reading first. */}`,
|
|
3500
3560
|
` <FileUpload`,
|
|
3501
|
-
` accept="
|
|
3561
|
+
` accept="${ATTACHMENT_ACCEPT}"`,
|
|
3502
3562
|
` onFilesAdded={async (files) => setStaged([...staged(), ...(await Promise.all(files.map(toAttachment)))])}`,
|
|
3503
3563
|
` >`,
|
|
3504
3564
|
` <FileUploadTrigger class="border-border text-muted-foreground w-full rounded-xl border border-dashed px-4 py-3 text-center text-sm">`,
|
package/src/wire/encode.ts
CHANGED
|
@@ -143,6 +143,7 @@ type SettledTool = ToolPart & { toolCallId: string };
|
|
|
143
143
|
|
|
144
144
|
const isTextPart = (p: MessagePart): p is TextPart => p.type === 'text';
|
|
145
145
|
|
|
146
|
+
// lint-silent-drops: drops reasoning,tool,card,source,file -- text projection by name and contract, used for the plain-string form of a turn; every caller that needs the other variants encodes them itself before falling back here.
|
|
146
147
|
const textOf = (parts: MessagePart[]): string =>
|
|
147
148
|
parts
|
|
148
149
|
.filter(isTextPart)
|
|
@@ -423,6 +424,7 @@ function reasoningDetailOf(part: ReasoningPart): OpenAIReasoningDetail | undefin
|
|
|
423
424
|
* or document content in an assistant message, so there is nothing to encode it
|
|
424
425
|
* to; attachments belong to the user turn that sent them.
|
|
425
426
|
*/
|
|
427
|
+
// lint-silent-drops: drops card,source -- kit-side parts with no OpenAI wire representation; cards come from tool calls, which ARE encoded, and sources are UI citations.
|
|
426
428
|
export function toOpenAIMessages(
|
|
427
429
|
messages: ChatMessage[],
|
|
428
430
|
options: OpenAIEncodeOptions = {},
|
|
@@ -449,6 +451,7 @@ export function toOpenAIMessages(
|
|
|
449
451
|
buffered = '';
|
|
450
452
|
};
|
|
451
453
|
|
|
454
|
+
// lint-silent-drops: drops reasoning,tool,card,source -- a USER turn carries authored content only; reasoning and tool parts are assistant-side, cards and sources are kit-side UI with no OpenAI wire form.
|
|
452
455
|
message.parts.forEach((part, partIndex) => {
|
|
453
456
|
if (part.type === 'text') {
|
|
454
457
|
buffered += part.text;
|
|
@@ -593,6 +596,7 @@ export function toAnthropicMessages(
|
|
|
593
596
|
if (message.role === 'user') {
|
|
594
597
|
// Part order, for the same reason as `toOpenAIMessages`.
|
|
595
598
|
const userBlocks: AnthropicContentBlock[] = [];
|
|
599
|
+
// lint-silent-drops: drops reasoning,tool,card,source -- a USER turn carries authored content only; reasoning and tool parts are assistant-side, cards and sources are kit-side UI with no Anthropic wire form.
|
|
596
600
|
message.parts.forEach((part, partIndex) => {
|
|
597
601
|
if (part.type === 'text') {
|
|
598
602
|
if (part.text !== '') userBlocks.push({ type: 'text', text: part.text });
|
|
@@ -619,6 +623,7 @@ export function toAnthropicMessages(
|
|
|
619
623
|
results = [];
|
|
620
624
|
};
|
|
621
625
|
|
|
626
|
+
// lint-silent-drops: drops card,source,file -- cards and sources are kit-side UI; a file part on an ASSISTANT turn has no Anthropic representation, since the API takes image and document content on user turns only.
|
|
622
627
|
message.parts.forEach((part, partIndex) => {
|
|
623
628
|
switch (part.type) {
|
|
624
629
|
case 'reasoning': {
|