kanna-code 0.17.0 → 0.19.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/client/assets/index-C952_xJD.css +32 -0
- package/dist/client/assets/index-lgfU0zw2.js +606 -0
- package/dist/client/chat-sounds/Blow.mp3 +0 -0
- package/dist/client/chat-sounds/Bottle.mp3 +0 -0
- package/dist/client/chat-sounds/Frog.mp3 +0 -0
- package/dist/client/chat-sounds/Funk.mp3 +0 -0
- package/dist/client/chat-sounds/Glass.mp3 +0 -0
- package/dist/client/chat-sounds/Ping.mp3 +0 -0
- package/dist/client/chat-sounds/Pop.mp3 +0 -0
- package/dist/client/chat-sounds/Purr.mp3 +0 -0
- package/dist/client/chat-sounds/Tink.mp3 +0 -0
- package/dist/client/index.html +2 -2
- package/package.json +2 -1
- package/src/server/agent.test.ts +153 -4
- package/src/server/agent.ts +76 -15
- package/src/server/event-store.test.ts +65 -0
- package/src/server/event-store.ts +27 -1
- package/src/server/events.ts +8 -0
- package/src/server/generate-title.ts +42 -2
- package/src/server/paths.ts +4 -0
- package/src/server/quick-response.test.ts +126 -3
- package/src/server/quick-response.ts +107 -14
- package/src/server/read-models.test.ts +4 -0
- package/src/server/read-models.ts +1 -0
- package/src/server/server.ts +178 -1
- package/src/server/title-generation.live.test.ts +44 -0
- package/src/server/uploads.test.ts +282 -0
- package/src/server/uploads.ts +127 -0
- package/src/server/ws-router.test.ts +181 -0
- package/src/server/ws-router.ts +18 -0
- package/src/shared/protocol.ts +3 -0
- package/src/shared/tools.test.ts +65 -0
- package/src/shared/tools.ts +76 -0
- package/src/shared/types.ts +34 -1
- package/dist/client/assets/index-CtKZIZdD.js +0 -533
- package/dist/client/assets/index-Cwex1U8S.css +0 -32
package/src/shared/tools.test.ts
CHANGED
|
@@ -96,4 +96,69 @@ describe("hydrateToolResult", () => {
|
|
|
96
96
|
|
|
97
97
|
expect(hydrateToolResult(tool, "line 1\nline 2")).toBe("line 1\nline 2")
|
|
98
98
|
})
|
|
99
|
+
|
|
100
|
+
test("hydrates read image results with canonical image blocks intact", () => {
|
|
101
|
+
const tool = normalizeToolCall({
|
|
102
|
+
toolName: "Read",
|
|
103
|
+
toolId: "tool-image",
|
|
104
|
+
input: { file_path: "/tmp/example.png" },
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
expect(hydrateToolResult(tool, {
|
|
108
|
+
content: [
|
|
109
|
+
{
|
|
110
|
+
type: "text",
|
|
111
|
+
text: "Read image file [image/png]\n[Image: original 10x10, displayed at 10x10.]",
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
type: "image",
|
|
115
|
+
data: "ZmFrZS1pbWFnZS1kYXRh",
|
|
116
|
+
mimeType: "image/png",
|
|
117
|
+
},
|
|
118
|
+
],
|
|
119
|
+
})).toEqual({
|
|
120
|
+
content: "Read image file [image/png]\n[Image: original 10x10, displayed at 10x10.]",
|
|
121
|
+
blocks: [
|
|
122
|
+
{
|
|
123
|
+
type: "text",
|
|
124
|
+
text: "Read image file [image/png]\n[Image: original 10x10, displayed at 10x10.]",
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
type: "image",
|
|
128
|
+
data: "ZmFrZS1pbWFnZS1kYXRh",
|
|
129
|
+
mimeType: "image/png",
|
|
130
|
+
},
|
|
131
|
+
],
|
|
132
|
+
})
|
|
133
|
+
})
|
|
134
|
+
|
|
135
|
+
test("hydrates Claude read image results with source.base64 into canonical image blocks", () => {
|
|
136
|
+
const tool = normalizeToolCall({
|
|
137
|
+
toolName: "Read",
|
|
138
|
+
toolId: "tool-image-claude",
|
|
139
|
+
input: { file_path: "/tmp/example.png" },
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
expect(hydrateToolResult(tool, {
|
|
143
|
+
content: [
|
|
144
|
+
{
|
|
145
|
+
type: "image",
|
|
146
|
+
source: {
|
|
147
|
+
type: "base64",
|
|
148
|
+
data: "ZmFrZS1pbWFnZS1kYXRh",
|
|
149
|
+
media_type: "image/png",
|
|
150
|
+
},
|
|
151
|
+
},
|
|
152
|
+
],
|
|
153
|
+
})).toEqual({
|
|
154
|
+
content: "",
|
|
155
|
+
blocks: [
|
|
156
|
+
{
|
|
157
|
+
type: "image",
|
|
158
|
+
data: "ZmFrZS1pbWFnZS1kYXRh",
|
|
159
|
+
mimeType: "image/png",
|
|
160
|
+
},
|
|
161
|
+
],
|
|
162
|
+
})
|
|
163
|
+
})
|
|
99
164
|
})
|
package/src/shared/tools.ts
CHANGED
|
@@ -203,6 +203,72 @@ function parseJsonValue(value: unknown): unknown {
|
|
|
203
203
|
}
|
|
204
204
|
}
|
|
205
205
|
|
|
206
|
+
type ReadStructuredTextBlock = {
|
|
207
|
+
type: "text"
|
|
208
|
+
text: string
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
type ReadStructuredImageBlock = {
|
|
212
|
+
type: "image"
|
|
213
|
+
data: string
|
|
214
|
+
mimeType?: string
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function normalizeReadBlocks(value: unknown): Array<ReadStructuredTextBlock | ReadStructuredImageBlock> {
|
|
218
|
+
const blocks = (
|
|
219
|
+
value
|
|
220
|
+
&& typeof value === "object"
|
|
221
|
+
&& "content" in value
|
|
222
|
+
&& Array.isArray((value as { content?: unknown }).content)
|
|
223
|
+
)
|
|
224
|
+
? (value as { content: unknown[] }).content
|
|
225
|
+
: Array.isArray(value)
|
|
226
|
+
? value
|
|
227
|
+
: []
|
|
228
|
+
|
|
229
|
+
const normalized: Array<ReadStructuredTextBlock | ReadStructuredImageBlock> = []
|
|
230
|
+
|
|
231
|
+
for (const block of blocks) {
|
|
232
|
+
if (!block || typeof block !== "object" || !("type" in block)) {
|
|
233
|
+
continue
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
if (block.type === "text" && typeof block.text === "string") {
|
|
237
|
+
normalized.push({ type: "text", text: block.text })
|
|
238
|
+
continue
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
if (block.type === "image") {
|
|
242
|
+
if ("data" in block && typeof block.data === "string") {
|
|
243
|
+
normalized.push({
|
|
244
|
+
type: "image",
|
|
245
|
+
data: block.data,
|
|
246
|
+
mimeType: typeof block.mimeType === "string" ? block.mimeType : undefined,
|
|
247
|
+
})
|
|
248
|
+
continue
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
if (
|
|
252
|
+
"source" in block
|
|
253
|
+
&& block.source
|
|
254
|
+
&& typeof block.source === "object"
|
|
255
|
+
&& "type" in block.source
|
|
256
|
+
&& block.source.type === "base64"
|
|
257
|
+
&& "data" in block.source
|
|
258
|
+
&& typeof block.source.data === "string"
|
|
259
|
+
) {
|
|
260
|
+
normalized.push({
|
|
261
|
+
type: "image",
|
|
262
|
+
data: block.source.data,
|
|
263
|
+
mimeType: typeof block.source.media_type === "string" ? block.source.media_type : undefined,
|
|
264
|
+
})
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
return normalized
|
|
270
|
+
}
|
|
271
|
+
|
|
206
272
|
export function hydrateToolResult(tool: NormalizedToolCall, raw: unknown): HydratedToolCall["result"] {
|
|
207
273
|
const parsed = parseJsonValue(raw)
|
|
208
274
|
|
|
@@ -241,6 +307,16 @@ export function hydrateToolResult(tool: NormalizedToolCall, raw: unknown): Hydra
|
|
|
241
307
|
if (typeof parsed === "string") {
|
|
242
308
|
return parsed
|
|
243
309
|
}
|
|
310
|
+
const blocks = normalizeReadBlocks(parsed)
|
|
311
|
+
if (blocks.length > 0) {
|
|
312
|
+
return {
|
|
313
|
+
content: blocks
|
|
314
|
+
.flatMap((block) => block.type === "text" ? [block.text] : [])
|
|
315
|
+
.join(""),
|
|
316
|
+
blocks,
|
|
317
|
+
} satisfies ReadFileToolResult
|
|
318
|
+
}
|
|
319
|
+
|
|
244
320
|
const record = asRecord(parsed)
|
|
245
321
|
return {
|
|
246
322
|
content: typeof record?.content === "string" ? record.content : JSON.stringify(parsed, null, 2),
|
package/src/shared/types.ts
CHANGED
|
@@ -3,6 +3,25 @@ export const PROTOCOL_VERSION = 1 as const
|
|
|
3
3
|
|
|
4
4
|
export type AgentProvider = "claude" | "codex"
|
|
5
5
|
|
|
6
|
+
export type AttachmentKind = "image" | "file"
|
|
7
|
+
|
|
8
|
+
export interface ChatAttachment {
|
|
9
|
+
id: string
|
|
10
|
+
kind: AttachmentKind
|
|
11
|
+
displayName: string
|
|
12
|
+
absolutePath: string
|
|
13
|
+
relativePath: string
|
|
14
|
+
contentUrl: string
|
|
15
|
+
mimeType: string
|
|
16
|
+
size: number
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface InternalUserAttachmentsData {
|
|
20
|
+
userText: string
|
|
21
|
+
attachments: ChatAttachment[]
|
|
22
|
+
llmHintText: string
|
|
23
|
+
}
|
|
24
|
+
|
|
6
25
|
export interface ProviderModelOption {
|
|
7
26
|
id: string
|
|
8
27
|
label: string
|
|
@@ -173,6 +192,7 @@ export interface SidebarChatRow {
|
|
|
173
192
|
chatId: string
|
|
174
193
|
title: string
|
|
175
194
|
status: KannaStatus
|
|
195
|
+
unread: boolean
|
|
176
196
|
localPath: string
|
|
177
197
|
provider: AgentProvider | null
|
|
178
198
|
lastMessageAt?: number
|
|
@@ -378,6 +398,7 @@ export interface ToolResultEntry extends TranscriptEntryBase {
|
|
|
378
398
|
export interface UserPromptEntry extends TranscriptEntryBase {
|
|
379
399
|
kind: "user_prompt"
|
|
380
400
|
content: string
|
|
401
|
+
attachments?: ChatAttachment[]
|
|
381
402
|
}
|
|
382
403
|
|
|
383
404
|
export interface SystemInitEntry extends TranscriptEntryBase {
|
|
@@ -501,8 +522,20 @@ export type HydratedBashToolCall =
|
|
|
501
522
|
export type HydratedWebSearchToolCall =
|
|
502
523
|
HydratedToolCallBase<"web_search", WebSearchToolCall["input"], unknown>
|
|
503
524
|
|
|
525
|
+
export interface ReadFileTextBlock {
|
|
526
|
+
type: "text"
|
|
527
|
+
text: string
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
export interface ReadFileImageBlock {
|
|
531
|
+
type: "image"
|
|
532
|
+
data: string
|
|
533
|
+
mimeType?: string
|
|
534
|
+
}
|
|
535
|
+
|
|
504
536
|
export interface ReadFileToolResult {
|
|
505
537
|
content: string
|
|
538
|
+
blocks?: Array<ReadFileTextBlock | ReadFileImageBlock>
|
|
506
539
|
}
|
|
507
540
|
|
|
508
541
|
export type HydratedReadFileToolCall =
|
|
@@ -540,7 +573,7 @@ export type HydratedToolCall =
|
|
|
540
573
|
| HydratedUnknownToolCall
|
|
541
574
|
|
|
542
575
|
export type HydratedTranscriptMessage =
|
|
543
|
-
| ({ kind: "user_prompt"; content: string; id: string; messageId?: string; timestamp: string; hidden?: boolean })
|
|
576
|
+
| ({ kind: "user_prompt"; content: string; attachments?: ChatAttachment[]; id: string; messageId?: string; timestamp: string; hidden?: boolean })
|
|
544
577
|
| ({ kind: "system_init"; model: string; tools: string[]; agents: string[]; slashCommands: string[]; mcpServers: McpServerInfo[]; provider: AgentProvider; id: string; messageId?: string; timestamp: string; hidden?: boolean; debugRaw?: string })
|
|
545
578
|
| ({ kind: "account_info"; accountInfo: AccountInfo; id: string; messageId?: string; timestamp: string; hidden?: boolean })
|
|
546
579
|
| ({ kind: "assistant_text"; text: string; id: string; messageId?: string; timestamp: string; hidden?: boolean })
|