kanna-code 0.41.5 → 0.41.6
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-CTT6qioa.js → index-DqIqYlzq.js} +197 -197
- package/dist/client/index.html +1 -1
- package/package.json +5 -3
- package/src/server/agent.ts +19 -0
- package/src/server/app-settings.test.ts +1 -1
- package/src/server/app-settings.ts +1 -1
- package/src/server/provider-catalog.test.ts +25 -4
- package/src/server/provider-catalog.ts +78 -9
- package/src/server/ws-router.test.ts +1 -1
- package/src/server/ws-router.ts +1 -1
- package/src/shared/types.test.ts +11 -2
- package/src/shared/types.ts +20 -5
package/dist/client/index.html
CHANGED
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
})()
|
|
26
26
|
</script>
|
|
27
27
|
<title>Kanna</title>
|
|
28
|
-
<script type="module" crossorigin src="/assets/index-
|
|
28
|
+
<script type="module" crossorigin src="/assets/index-DqIqYlzq.js"></script>
|
|
29
29
|
<link rel="stylesheet" crossorigin href="/assets/index-BujIAIt0.css">
|
|
30
30
|
</head>
|
|
31
31
|
<body>
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "kanna-code",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.41.
|
|
4
|
+
"version": "0.41.6",
|
|
5
5
|
"description": "A beautiful web UI for Claude Code",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"keywords": [
|
|
@@ -48,7 +48,8 @@
|
|
|
48
48
|
"prepublishOnly": "bun run build"
|
|
49
49
|
},
|
|
50
50
|
"dependencies": {
|
|
51
|
-
"@anthropic-ai/claude-agent-sdk": "^0.
|
|
51
|
+
"@anthropic-ai/claude-agent-sdk": "^0.3.170",
|
|
52
|
+
"@anthropic-ai/sdk": "^0.104.0",
|
|
52
53
|
"@legendapp/list": "3.0.0-beta.44",
|
|
53
54
|
"@pierre/diffs": "^1.1.12",
|
|
54
55
|
"@radix-ui/react-context-menu": "^2.2.16",
|
|
@@ -63,7 +64,8 @@
|
|
|
63
64
|
"file-type": "^22.0.0",
|
|
64
65
|
"openai": "^6.34.0",
|
|
65
66
|
"react-resizable-panels": "^4.7.3",
|
|
66
|
-
"uqr": "^0.1.3"
|
|
67
|
+
"uqr": "^0.1.3",
|
|
68
|
+
"zod": "^4.4.3"
|
|
67
69
|
},
|
|
68
70
|
"devDependencies": {
|
|
69
71
|
"@dnd-kit/core": "^6.3.1",
|
package/src/server/agent.ts
CHANGED
|
@@ -20,6 +20,8 @@ import { CodexAppServerManager } from "./codex-app-server"
|
|
|
20
20
|
import { type GenerateChatTitleResult, generateTitleForChatDetailed } from "./generate-title"
|
|
21
21
|
import type { HarnessEvent, HarnessToolRequest, HarnessTurn } from "./harness-types"
|
|
22
22
|
import {
|
|
23
|
+
applyClaudeSdkModels,
|
|
24
|
+
type ClaudeSdkModelInfo,
|
|
23
25
|
codexServiceTierFromModelOptions,
|
|
24
26
|
getServerProviderCatalog,
|
|
25
27
|
normalizeClaudeModelOptions,
|
|
@@ -82,6 +84,7 @@ interface ClaudeSessionHandle {
|
|
|
82
84
|
sendPrompt: (content: string) => Promise<void>
|
|
83
85
|
setModel: (model: string) => Promise<void>
|
|
84
86
|
setPermissionMode: (planMode: boolean) => Promise<void>
|
|
87
|
+
supportedModels?: () => Promise<ClaudeSdkModelInfo[]>
|
|
85
88
|
}
|
|
86
89
|
|
|
87
90
|
interface ClaudeSessionState {
|
|
@@ -664,6 +667,7 @@ async function startClaudeSession(args: {
|
|
|
664
667
|
setPermissionMode: async (planMode: boolean) => {
|
|
665
668
|
await q.setPermissionMode(planMode ? "plan" : "acceptEdits")
|
|
666
669
|
},
|
|
670
|
+
supportedModels: async () => await q.supportedModels(),
|
|
667
671
|
close: () => {
|
|
668
672
|
promptQueue.close()
|
|
669
673
|
q.close()
|
|
@@ -718,6 +722,20 @@ export class AgentCoordinator {
|
|
|
718
722
|
this.onStateChange(chatId, options)
|
|
719
723
|
}
|
|
720
724
|
|
|
725
|
+
private refreshClaudeModelCatalog(session: ClaudeSessionHandle) {
|
|
726
|
+
if (!session.supportedModels) return
|
|
727
|
+
void session.supportedModels()
|
|
728
|
+
.then((models) => {
|
|
729
|
+
if (applyClaudeSdkModels(models)) {
|
|
730
|
+
this.emitStateChange(undefined, { immediate: true })
|
|
731
|
+
}
|
|
732
|
+
})
|
|
733
|
+
.catch((error) => {
|
|
734
|
+
const message = error instanceof Error ? error.message : String(error)
|
|
735
|
+
this.reportBackgroundError?.(`[claude-models] failed to refresh Claude model catalog: ${message}`)
|
|
736
|
+
})
|
|
737
|
+
}
|
|
738
|
+
|
|
721
739
|
getActiveTurnProfile(chatId: string): SendToStartingProfile | null {
|
|
722
740
|
const active = this.activeTurns.get(chatId)
|
|
723
741
|
if (!active?.clientTraceId || active.profilingStartedAt === undefined) {
|
|
@@ -1078,6 +1096,7 @@ export class AgentCoordinator {
|
|
|
1078
1096
|
forkSession: args.forkSession,
|
|
1079
1097
|
onToolRequest: args.onToolRequest,
|
|
1080
1098
|
})
|
|
1099
|
+
this.refreshClaudeModelCatalog(started)
|
|
1081
1100
|
|
|
1082
1101
|
session = {
|
|
1083
1102
|
id: crypto.randomUUID(),
|
|
@@ -36,7 +36,7 @@ function expectedSettingsSnapshot(filePath: string, overrides: Partial<AppSettin
|
|
|
36
36
|
defaultProvider: "last_used",
|
|
37
37
|
providerDefaults: {
|
|
38
38
|
claude: {
|
|
39
|
-
model: "claude-opus-4-
|
|
39
|
+
model: "claude-opus-4-8",
|
|
40
40
|
modelOptions: {
|
|
41
41
|
reasoningEffort: "high",
|
|
42
42
|
contextWindow: "200k",
|
|
@@ -99,7 +99,7 @@ function getDefaultEditorCommandTemplate(preset: EditorPreset) {
|
|
|
99
99
|
function createDefaultProviderDefaults(): ChatProviderPreferences {
|
|
100
100
|
return {
|
|
101
101
|
claude: {
|
|
102
|
-
model: "claude-opus-4-
|
|
102
|
+
model: "claude-opus-4-8",
|
|
103
103
|
modelOptions: { ...DEFAULT_CLAUDE_MODEL_OPTIONS },
|
|
104
104
|
planMode: false,
|
|
105
105
|
},
|
|
@@ -1,15 +1,22 @@
|
|
|
1
|
-
import { describe, expect, test } from "bun:test"
|
|
1
|
+
import { afterEach, describe, expect, test } from "bun:test"
|
|
2
2
|
import {
|
|
3
|
+
SERVER_PROVIDERS,
|
|
4
|
+
applyClaudeSdkModels,
|
|
3
5
|
codexServiceTierFromModelOptions,
|
|
4
6
|
normalizeClaudeModelOptions,
|
|
5
7
|
normalizeCodexModelOptions,
|
|
6
8
|
normalizeServerModel,
|
|
9
|
+
resetServerProvidersForTests,
|
|
7
10
|
} from "./provider-catalog"
|
|
8
11
|
import { resolveClaudeApiModelId } from "../shared/types"
|
|
9
12
|
|
|
10
13
|
describe("provider catalog normalization", () => {
|
|
14
|
+
afterEach(() => {
|
|
15
|
+
resetServerProvidersForTests()
|
|
16
|
+
})
|
|
17
|
+
|
|
11
18
|
test("maps legacy Claude effort into shared model options", () => {
|
|
12
|
-
expect(normalizeClaudeModelOptions("claude-opus-4-
|
|
19
|
+
expect(normalizeClaudeModelOptions("claude-opus-4-8", undefined, "max")).toEqual({
|
|
13
20
|
reasoningEffort: "max",
|
|
14
21
|
contextWindow: "200k",
|
|
15
22
|
})
|
|
@@ -58,12 +65,26 @@ describe("provider catalog normalization", () => {
|
|
|
58
65
|
|
|
59
66
|
test("normalizes server model ids through the shared alias catalog", () => {
|
|
60
67
|
expect(normalizeServerModel("codex")).toBe("gpt-5.5")
|
|
61
|
-
expect(normalizeServerModel("claude", "
|
|
68
|
+
expect(normalizeServerModel("claude", "fable")).toBe("fable")
|
|
69
|
+
expect(normalizeServerModel("claude", "opus")).toBe("claude-opus-4-8")
|
|
62
70
|
expect(normalizeServerModel("codex", "gpt-5-codex")).toBe("gpt-5.3-codex")
|
|
63
71
|
})
|
|
64
72
|
|
|
65
73
|
test("resolves Claude API model ids for 1m context window", () => {
|
|
66
|
-
expect(resolveClaudeApiModelId("claude-opus-4-
|
|
74
|
+
expect(resolveClaudeApiModelId("claude-opus-4-8", "1m")).toBe("claude-opus-4-8[1m]")
|
|
75
|
+
expect(resolveClaudeApiModelId("fable", "200k")).toBe("fable")
|
|
67
76
|
expect(resolveClaudeApiModelId("claude-sonnet-4-6", "200k")).toBe("claude-sonnet-4-6")
|
|
68
77
|
})
|
|
78
|
+
|
|
79
|
+
test("overlays Claude model labels from the Agent SDK model catalog", () => {
|
|
80
|
+
expect(applyClaudeSdkModels([
|
|
81
|
+
{ value: "claude-fable-5[1m]", displayName: "Fable from SDK", supportsEffort: true },
|
|
82
|
+
{ value: "claude-opus-4-7", displayName: "Opus 4.7", supportsEffort: true },
|
|
83
|
+
{ value: "claude-opus-4-8", displayName: "Opus from SDK", supportsEffort: true },
|
|
84
|
+
])).toBe(true)
|
|
85
|
+
|
|
86
|
+
const claude = SERVER_PROVIDERS.find((provider) => provider.id === "claude")
|
|
87
|
+
expect(claude?.models.find((model) => model.id === "fable")?.label).toBe("Fable from SDK")
|
|
88
|
+
expect(claude?.models.find((model) => model.id === "claude-opus-4-8")?.label).toBe("Opus from SDK")
|
|
89
|
+
})
|
|
69
90
|
})
|
|
@@ -25,15 +25,84 @@ const HARD_CODED_CODEX_MODELS: ProviderModelOption[] = [
|
|
|
25
25
|
{ id: "gpt-5.3-codex-spark", label: "GPT-5.3 Codex Spark", supportsEffort: false },
|
|
26
26
|
]
|
|
27
27
|
|
|
28
|
-
export
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
28
|
+
export interface ClaudeSdkModelInfo {
|
|
29
|
+
value: string
|
|
30
|
+
displayName?: string
|
|
31
|
+
description?: string
|
|
32
|
+
supportsEffort?: boolean
|
|
33
|
+
supportedEffortLevels?: readonly string[]
|
|
34
|
+
supportsAdaptiveThinking?: boolean
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function createServerProviders(): ProviderCatalogEntry[] {
|
|
38
|
+
return PROVIDERS.map((provider) =>
|
|
39
|
+
provider.id === "codex"
|
|
40
|
+
? {
|
|
41
|
+
...provider,
|
|
42
|
+
defaultModel: "gpt-5.5",
|
|
43
|
+
models: HARD_CODED_CODEX_MODELS,
|
|
44
|
+
}
|
|
45
|
+
: provider
|
|
46
|
+
)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export const SERVER_PROVIDERS: ProviderCatalogEntry[] = createServerProviders()
|
|
50
|
+
|
|
51
|
+
export function resetServerProvidersForTests() {
|
|
52
|
+
SERVER_PROVIDERS.splice(0, SERVER_PROVIDERS.length, ...createServerProviders())
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function modelFamily(value: string) {
|
|
56
|
+
const match = value.match(/^(?:claude-)?([a-z]+)(?:-|$)/i)
|
|
57
|
+
return match?.[1]?.toLowerCase() ?? value.toLowerCase()
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function sdkModelMatchScore(model: ClaudeSdkModelInfo, option: ProviderModelOption) {
|
|
61
|
+
const modelValue = model.value.toLowerCase()
|
|
62
|
+
if (modelValue === option.id.toLowerCase()) return 3
|
|
63
|
+
if (option.aliases?.some((alias) => alias.toLowerCase() === modelValue)) return 2
|
|
64
|
+
const optionKeys = [option.id, ...(option.aliases ?? [])].map(modelFamily)
|
|
65
|
+
return optionKeys.includes(modelFamily(model.value)) ? 1 : 0
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function findSdkModelForOption(models: readonly ClaudeSdkModelInfo[], option: ProviderModelOption) {
|
|
69
|
+
let bestModel: ClaudeSdkModelInfo | undefined
|
|
70
|
+
let bestScore = 0
|
|
71
|
+
for (const model of models) {
|
|
72
|
+
const score = sdkModelMatchScore(model, option)
|
|
73
|
+
if (score > bestScore) {
|
|
74
|
+
bestModel = model
|
|
75
|
+
bestScore = score
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return bestModel
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function applyClaudeSdkModels(models: readonly ClaudeSdkModelInfo[]) {
|
|
82
|
+
const claudeIndex = SERVER_PROVIDERS.findIndex((provider) => provider.id === "claude")
|
|
83
|
+
const claudeProvider = SERVER_PROVIDERS[claudeIndex]
|
|
84
|
+
if (!claudeProvider) return false
|
|
85
|
+
|
|
86
|
+
const nextModels = claudeProvider.models.map((option) => {
|
|
87
|
+
const sdkModel = findSdkModelForOption(models, option)
|
|
88
|
+
if (!sdkModel) return option
|
|
89
|
+
return {
|
|
90
|
+
...option,
|
|
91
|
+
label: sdkModel.displayName?.trim() || option.label,
|
|
92
|
+
supportsEffort: sdkModel.supportsEffort ?? option.supportsEffort,
|
|
93
|
+
}
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
if (JSON.stringify(nextModels) === JSON.stringify(claudeProvider.models)) {
|
|
97
|
+
return false
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
SERVER_PROVIDERS.splice(claudeIndex, 1, {
|
|
101
|
+
...claudeProvider,
|
|
102
|
+
models: nextModels,
|
|
103
|
+
})
|
|
104
|
+
return true
|
|
105
|
+
}
|
|
37
106
|
|
|
38
107
|
export function getServerProviderCatalog(provider: AgentProvider): ProviderCatalogEntry {
|
|
39
108
|
const entry = SERVER_PROVIDERS.find((candidate) => candidate.id === provider)
|
|
@@ -87,7 +87,7 @@ const DEFAULT_APP_SETTINGS_SNAPSHOT: AppSettingsSnapshot = {
|
|
|
87
87
|
defaultProvider: "last_used",
|
|
88
88
|
providerDefaults: {
|
|
89
89
|
claude: {
|
|
90
|
-
model: "claude-opus-4-
|
|
90
|
+
model: "claude-opus-4-8",
|
|
91
91
|
modelOptions: {
|
|
92
92
|
reasoningEffort: "high",
|
|
93
93
|
contextWindow: "200k",
|
package/src/server/ws-router.ts
CHANGED
package/src/shared/types.test.ts
CHANGED
|
@@ -1,13 +1,21 @@
|
|
|
1
1
|
import { describe, expect, test } from "bun:test"
|
|
2
2
|
import {
|
|
3
|
+
deriveClaudeModelLabel,
|
|
3
4
|
normalizeClaudeModelId,
|
|
4
5
|
normalizeCodexModelId,
|
|
5
6
|
supportsClaudeMaxReasoningEffort,
|
|
6
7
|
} from "./types"
|
|
7
8
|
|
|
8
9
|
describe("shared model normalization", () => {
|
|
10
|
+
test("derives fallback Claude model labels from model ids", () => {
|
|
11
|
+
expect(deriveClaudeModelLabel("fable")).toBe("Fable")
|
|
12
|
+
expect(deriveClaudeModelLabel("claude-opus-4-8")).toBe("Opus")
|
|
13
|
+
expect(deriveClaudeModelLabel("claude-haiku-4-5-20251001")).toBe("Haiku")
|
|
14
|
+
})
|
|
15
|
+
|
|
9
16
|
test("normalizes Claude aliases via the provider catalog", () => {
|
|
10
|
-
expect(normalizeClaudeModelId("
|
|
17
|
+
expect(normalizeClaudeModelId("fable")).toBe("fable")
|
|
18
|
+
expect(normalizeClaudeModelId("opus")).toBe("claude-opus-4-8")
|
|
11
19
|
expect(normalizeClaudeModelId("sonnet")).toBe("claude-sonnet-4-6")
|
|
12
20
|
expect(normalizeClaudeModelId("haiku")).toBe("claude-haiku-4-5-20251001")
|
|
13
21
|
})
|
|
@@ -18,8 +26,9 @@ describe("shared model normalization", () => {
|
|
|
18
26
|
})
|
|
19
27
|
|
|
20
28
|
test("uses declarative metadata for Claude max-effort support", () => {
|
|
21
|
-
expect(supportsClaudeMaxReasoningEffort("claude-opus-4-
|
|
29
|
+
expect(supportsClaudeMaxReasoningEffort("claude-opus-4-8")).toBe(true)
|
|
22
30
|
expect(supportsClaudeMaxReasoningEffort("opus")).toBe(true)
|
|
31
|
+
expect(supportsClaudeMaxReasoningEffort("fable")).toBe(false)
|
|
23
32
|
expect(supportsClaudeMaxReasoningEffort("claude-sonnet-4-6")).toBe(false)
|
|
24
33
|
})
|
|
25
34
|
})
|
package/src/shared/types.ts
CHANGED
|
@@ -228,6 +228,16 @@ export function isClaudeContextWindow(value: unknown): value is ClaudeContextWin
|
|
|
228
228
|
return CLAUDE_CONTEXT_WINDOW_OPTIONS.some((option) => option.id === value)
|
|
229
229
|
}
|
|
230
230
|
|
|
231
|
+
function titleCaseWord(value: string) {
|
|
232
|
+
return value.length === 0 ? value : `${value[0]?.toUpperCase() ?? ""}${value.slice(1)}`
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
export function deriveClaudeModelLabel(modelId: string): string {
|
|
236
|
+
const parts = modelId.replace(/^claude-/, "").split("-").filter(Boolean)
|
|
237
|
+
if (parts.length === 0) return modelId
|
|
238
|
+
return titleCaseWord(parts[0] ?? modelId)
|
|
239
|
+
}
|
|
240
|
+
|
|
231
241
|
export interface ProviderCatalogEntry {
|
|
232
242
|
id: AgentProvider
|
|
233
243
|
label: string
|
|
@@ -247,8 +257,13 @@ export const PROVIDERS: ProviderCatalogEntry[] = [
|
|
|
247
257
|
supportsPlanMode: true,
|
|
248
258
|
models: [
|
|
249
259
|
{
|
|
250
|
-
id: "
|
|
251
|
-
label: "
|
|
260
|
+
id: "fable",
|
|
261
|
+
label: deriveClaudeModelLabel("fable"),
|
|
262
|
+
supportsEffort: true,
|
|
263
|
+
},
|
|
264
|
+
{
|
|
265
|
+
id: "claude-opus-4-8",
|
|
266
|
+
label: deriveClaudeModelLabel("claude-opus-4-8"),
|
|
252
267
|
supportsEffort: true,
|
|
253
268
|
aliases: ["opus"],
|
|
254
269
|
contextWindowOptions: [...CLAUDE_CONTEXT_WINDOW_OPTIONS],
|
|
@@ -256,14 +271,14 @@ export const PROVIDERS: ProviderCatalogEntry[] = [
|
|
|
256
271
|
},
|
|
257
272
|
{
|
|
258
273
|
id: "claude-sonnet-4-6",
|
|
259
|
-
label: "
|
|
274
|
+
label: deriveClaudeModelLabel("claude-sonnet-4-6"),
|
|
260
275
|
supportsEffort: true,
|
|
261
276
|
aliases: ["sonnet"],
|
|
262
277
|
contextWindowOptions: [...CLAUDE_CONTEXT_WINDOW_OPTIONS],
|
|
263
278
|
},
|
|
264
279
|
{
|
|
265
280
|
id: "claude-haiku-4-5-20251001",
|
|
266
|
-
label: "
|
|
281
|
+
label: deriveClaudeModelLabel("claude-haiku-4-5-20251001"),
|
|
267
282
|
supportsEffort: true,
|
|
268
283
|
aliases: ["haiku"],
|
|
269
284
|
},
|
|
@@ -311,7 +326,7 @@ export function normalizeProviderModelId(
|
|
|
311
326
|
?? getProviderCatalog(provider).defaultModel
|
|
312
327
|
}
|
|
313
328
|
|
|
314
|
-
export function normalizeClaudeModelId(modelId?: string, fallbackModelId = "claude-opus-4-
|
|
329
|
+
export function normalizeClaudeModelId(modelId?: string, fallbackModelId = "claude-opus-4-8"): string {
|
|
315
330
|
return normalizeProviderModelId("claude", modelId, fallbackModelId)
|
|
316
331
|
}
|
|
317
332
|
|