kanna-code 0.41.7 → 0.42.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-BujIAIt0.css → index-COpRUlCZ.css} +1 -1
- package/dist/client/assets/{index-BWrXLfF8.js → index-DDpLGxoy.js} +205 -205
- package/dist/client/index.html +2 -2
- package/dist/export-viewer/assets/{index-Bw6GI0aH.css → index-DH7NuXKA.css} +1 -1
- package/dist/export-viewer/assets/{index--p-u0wfg.js → index-DoK2sc-h.js} +51 -51
- package/dist/export-viewer/index.html +2 -2
- package/package.json +1 -1
- package/src/server/agent.test.ts +1 -8
- package/src/server/agent.ts +41 -22
- package/src/server/app-settings.test.ts +48 -2
- package/src/server/app-settings.ts +43 -8
- package/src/server/async-queue.ts +45 -0
- package/src/server/codex-app-server.test.ts +70 -1
- package/src/server/codex-app-server.ts +4 -62
- package/src/server/cursor-cli.test.ts +219 -0
- package/src/server/cursor-cli.ts +376 -0
- package/src/server/paths.ts +64 -0
- package/src/server/provider-catalog.test.ts +31 -4
- package/src/server/provider-catalog.ts +28 -22
- package/src/server/read-models.test.ts +17 -0
- package/src/server/read-models.ts +2 -0
- package/src/server/transcript.ts +14 -0
- package/src/server/ws-router.test.ts +7 -0
- package/src/server/ws-router.ts +26 -3
- package/src/shared/assert.ts +7 -0
- package/src/shared/git-url.ts +54 -0
- package/src/shared/json.ts +14 -0
- package/src/shared/protocol.ts +1 -0
- package/src/shared/types.test.ts +50 -1
- package/src/shared/types.ts +150 -15
|
@@ -3,8 +3,10 @@ import {
|
|
|
3
3
|
SERVER_PROVIDERS,
|
|
4
4
|
applyClaudeSdkModels,
|
|
5
5
|
codexServiceTierFromModelOptions,
|
|
6
|
+
cursorModelIdForOptions,
|
|
6
7
|
normalizeClaudeModelOptions,
|
|
7
8
|
normalizeCodexModelOptions,
|
|
9
|
+
normalizeCursorModelOptions,
|
|
8
10
|
normalizeServerModel,
|
|
9
11
|
resetServerProvidersForTests,
|
|
10
12
|
} from "./provider-catalog"
|
|
@@ -44,12 +46,12 @@ describe("provider catalog normalization", () => {
|
|
|
44
46
|
})
|
|
45
47
|
|
|
46
48
|
test("normalizes Codex model options and fast mode defaults", () => {
|
|
47
|
-
expect(normalizeCodexModelOptions(undefined)).toEqual({
|
|
48
|
-
reasoningEffort: "
|
|
49
|
+
expect(normalizeCodexModelOptions("gpt-5.6-sol", undefined)).toEqual({
|
|
50
|
+
reasoningEffort: "medium",
|
|
49
51
|
fastMode: false,
|
|
50
52
|
})
|
|
51
53
|
|
|
52
|
-
const normalized = normalizeCodexModelOptions({
|
|
54
|
+
const normalized = normalizeCodexModelOptions("gpt-5.6-terra", {
|
|
53
55
|
codex: {
|
|
54
56
|
reasoningEffort: "xhigh",
|
|
55
57
|
fastMode: true,
|
|
@@ -61,13 +63,38 @@ describe("provider catalog normalization", () => {
|
|
|
61
63
|
fastMode: true,
|
|
62
64
|
})
|
|
63
65
|
expect(codexServiceTierFromModelOptions(normalized)).toBe("fast")
|
|
66
|
+
|
|
67
|
+
expect(normalizeCodexModelOptions("gpt-5.6-sol", {
|
|
68
|
+
codex: { reasoningEffort: "ultra" },
|
|
69
|
+
}).reasoningEffort).toBe("ultra")
|
|
70
|
+
expect(normalizeCodexModelOptions("gpt-5.6-luna", {
|
|
71
|
+
codex: { reasoningEffort: "ultra" },
|
|
72
|
+
}).reasoningEffort).toBe("max")
|
|
73
|
+
expect(normalizeCodexModelOptions("gpt-5.6-luna", undefined, "minimal").reasoningEffort).toBe("low")
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
test("normalizes Cursor model options and applies the fast model suffix", () => {
|
|
77
|
+
expect(normalizeCursorModelOptions(undefined)).toEqual({ fastMode: false })
|
|
78
|
+
expect(normalizeCursorModelOptions({ cursor: { fastMode: true } })).toEqual({ fastMode: true })
|
|
79
|
+
|
|
80
|
+
expect(cursorModelIdForOptions("composer-2.5", { fastMode: false })).toBe("composer-2.5")
|
|
81
|
+
expect(cursorModelIdForOptions("composer-2.5", { fastMode: true })).toBe("composer-2.5-fast")
|
|
82
|
+
// Idempotent if the base id already carries the suffix.
|
|
83
|
+
expect(cursorModelIdForOptions("composer-2.5-fast", { fastMode: true })).toBe("composer-2.5-fast")
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
test("resolves the Cursor default model through the server catalog", () => {
|
|
87
|
+
// Exercises the catalog lookup + default fallback (throws if "cursor" is unregistered).
|
|
88
|
+
expect(normalizeServerModel("cursor")).toBe("composer-2.5")
|
|
89
|
+
expect(normalizeServerModel("cursor", "composer-2.5-fast")).toBe("composer-2.5")
|
|
64
90
|
})
|
|
65
91
|
|
|
66
92
|
test("normalizes server model ids through the shared alias catalog", () => {
|
|
67
|
-
expect(normalizeServerModel("codex")).toBe("gpt-5.
|
|
93
|
+
expect(normalizeServerModel("codex")).toBe("gpt-5.6-sol")
|
|
68
94
|
expect(normalizeServerModel("claude", "fable")).toBe("fable")
|
|
69
95
|
expect(normalizeServerModel("claude", "opus")).toBe("claude-opus-4-8")
|
|
70
96
|
expect(normalizeServerModel("codex", "gpt-5-codex")).toBe("gpt-5.3-codex")
|
|
97
|
+
expect(normalizeServerModel("codex", "gpt-5.6")).toBe("gpt-5.6-sol")
|
|
71
98
|
})
|
|
72
99
|
|
|
73
100
|
test("resolves Claude API model ids for 1m context window", () => {
|
|
@@ -2,6 +2,7 @@ import type {
|
|
|
2
2
|
AgentProvider,
|
|
3
3
|
ClaudeModelOptions,
|
|
4
4
|
CodexModelOptions,
|
|
5
|
+
CursorModelOptions,
|
|
5
6
|
ClaudeContextWindow,
|
|
6
7
|
ModelOptions,
|
|
7
8
|
ProviderCatalogEntry,
|
|
@@ -11,20 +12,15 @@ import type {
|
|
|
11
12
|
import {
|
|
12
13
|
DEFAULT_CLAUDE_MODEL_OPTIONS,
|
|
13
14
|
DEFAULT_CODEX_MODEL_OPTIONS,
|
|
15
|
+
DEFAULT_CURSOR_MODEL_OPTIONS,
|
|
14
16
|
PROVIDERS,
|
|
15
17
|
normalizeClaudeContextWindow,
|
|
18
|
+
normalizeCodexReasoningEffort,
|
|
16
19
|
normalizeProviderModelId,
|
|
17
20
|
isClaudeReasoningEffort,
|
|
18
21
|
isCodexReasoningEffort,
|
|
19
22
|
} from "../shared/types"
|
|
20
23
|
|
|
21
|
-
const HARD_CODED_CODEX_MODELS: ProviderModelOption[] = [
|
|
22
|
-
{ id: "gpt-5.5", label: "GPT-5.5", supportsEffort: false },
|
|
23
|
-
{ id: "gpt-5.4", label: "GPT-5.4", supportsEffort: false },
|
|
24
|
-
{ id: "gpt-5.3-codex", label: "GPT-5.3 Codex", supportsEffort: false },
|
|
25
|
-
{ id: "gpt-5.3-codex-spark", label: "GPT-5.3 Codex Spark", supportsEffort: false },
|
|
26
|
-
]
|
|
27
|
-
|
|
28
24
|
export interface ClaudeSdkModelInfo {
|
|
29
25
|
value: string
|
|
30
26
|
displayName?: string
|
|
@@ -35,15 +31,7 @@ export interface ClaudeSdkModelInfo {
|
|
|
35
31
|
}
|
|
36
32
|
|
|
37
33
|
function createServerProviders(): ProviderCatalogEntry[] {
|
|
38
|
-
return PROVIDERS
|
|
39
|
-
provider.id === "codex"
|
|
40
|
-
? {
|
|
41
|
-
...provider,
|
|
42
|
-
defaultModel: "gpt-5.5",
|
|
43
|
-
models: HARD_CODED_CODEX_MODELS,
|
|
44
|
-
}
|
|
45
|
-
: provider
|
|
46
|
-
)
|
|
34
|
+
return structuredClone(PROVIDERS)
|
|
47
35
|
}
|
|
48
36
|
|
|
49
37
|
export const SERVER_PROVIDERS: ProviderCatalogEntry[] = createServerProviders()
|
|
@@ -137,14 +125,17 @@ export function normalizeClaudeModelOptions(
|
|
|
137
125
|
}
|
|
138
126
|
}
|
|
139
127
|
|
|
140
|
-
export function normalizeCodexModelOptions(
|
|
128
|
+
export function normalizeCodexModelOptions(
|
|
129
|
+
model: string,
|
|
130
|
+
modelOptions?: ModelOptions,
|
|
131
|
+
legacyEffort?: string,
|
|
132
|
+
): CodexModelOptions {
|
|
141
133
|
const reasoningEffort = modelOptions?.codex?.reasoningEffort
|
|
142
134
|
return {
|
|
143
|
-
reasoningEffort:
|
|
144
|
-
|
|
145
|
-
:
|
|
146
|
-
|
|
147
|
-
: DEFAULT_CODEX_MODEL_OPTIONS.reasoningEffort,
|
|
135
|
+
reasoningEffort: normalizeCodexReasoningEffort(
|
|
136
|
+
model,
|
|
137
|
+
isCodexReasoningEffort(reasoningEffort) ? reasoningEffort : legacyEffort,
|
|
138
|
+
),
|
|
148
139
|
fastMode: typeof modelOptions?.codex?.fastMode === "boolean"
|
|
149
140
|
? modelOptions.codex.fastMode
|
|
150
141
|
: DEFAULT_CODEX_MODEL_OPTIONS.fastMode,
|
|
@@ -154,3 +145,18 @@ export function normalizeCodexModelOptions(modelOptions?: ModelOptions, legacyEf
|
|
|
154
145
|
export function codexServiceTierFromModelOptions(modelOptions: CodexModelOptions): ServiceTier | undefined {
|
|
155
146
|
return modelOptions.fastMode ? "fast" : undefined
|
|
156
147
|
}
|
|
148
|
+
|
|
149
|
+
export function normalizeCursorModelOptions(modelOptions?: ModelOptions): CursorModelOptions {
|
|
150
|
+
return {
|
|
151
|
+
fastMode: typeof modelOptions?.cursor?.fastMode === "boolean"
|
|
152
|
+
? modelOptions.cursor.fastMode
|
|
153
|
+
: DEFAULT_CURSOR_MODEL_OPTIONS.fastMode,
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Cursor encodes "fast" in the model id itself (composer-2.5 vs composer-2.5-fast),
|
|
158
|
+
// so we apply the suffix at spawn time rather than tracking a separate service tier.
|
|
159
|
+
export function cursorModelIdForOptions(baseModel: string, modelOptions: CursorModelOptions): string {
|
|
160
|
+
if (!modelOptions.fastMode) return baseModel
|
|
161
|
+
return baseModel.endsWith("-fast") ? baseModel : `${baseModel}-fast`
|
|
162
|
+
}
|
|
@@ -146,6 +146,9 @@ describe("read models", () => {
|
|
|
146
146
|
expect(chat?.history.recentLimit).toBe(200)
|
|
147
147
|
expect(chat?.availableProviders.length).toBeGreaterThan(1)
|
|
148
148
|
expect(chat?.availableProviders.find((provider) => provider.id === "codex")?.models.map((model) => model.id)).toEqual([
|
|
149
|
+
"gpt-5.6-sol",
|
|
150
|
+
"gpt-5.6-terra",
|
|
151
|
+
"gpt-5.6-luna",
|
|
149
152
|
"gpt-5.5",
|
|
150
153
|
"gpt-5.4",
|
|
151
154
|
"gpt-5.3-codex",
|
|
@@ -397,6 +400,18 @@ describe("read models", () => {
|
|
|
397
400
|
sessionToken: "thread-1",
|
|
398
401
|
lastTurnOutcome: null,
|
|
399
402
|
})
|
|
403
|
+
state.chatsById.set("chat-cursor", {
|
|
404
|
+
id: "chat-cursor",
|
|
405
|
+
projectId: "project-1",
|
|
406
|
+
title: "Cursor",
|
|
407
|
+
createdAt: 4,
|
|
408
|
+
updatedAt: 4,
|
|
409
|
+
unread: false,
|
|
410
|
+
provider: "cursor",
|
|
411
|
+
planMode: false,
|
|
412
|
+
sessionToken: "cursor-session",
|
|
413
|
+
lastTurnOutcome: null,
|
|
414
|
+
})
|
|
400
415
|
|
|
401
416
|
const sidebar = deriveSidebarData(
|
|
402
417
|
state,
|
|
@@ -407,5 +422,7 @@ describe("read models", () => {
|
|
|
407
422
|
expect(sidebar.projectGroups[0]?.chats.find((chat) => chat.chatId === "chat-active")?.canFork).toBeUndefined()
|
|
408
423
|
expect(sidebar.projectGroups[0]?.chats.find((chat) => chat.chatId === "chat-pending")?.canFork).toBe(true)
|
|
409
424
|
expect(sidebar.projectGroups[0]?.chats.find((chat) => chat.chatId === "chat-draining")?.canFork).toBeUndefined()
|
|
425
|
+
// Cursor has no fork primitive, so forking is disabled even with a live session.
|
|
426
|
+
expect(sidebar.projectGroups[0]?.chats.find((chat) => chat.chatId === "chat-cursor")?.canFork).toBeUndefined()
|
|
410
427
|
})
|
|
411
428
|
})
|
|
@@ -31,6 +31,8 @@ function canForkChat(
|
|
|
31
31
|
drainingChatIds: Set<string>,
|
|
32
32
|
) {
|
|
33
33
|
if (!chat.provider) return false
|
|
34
|
+
// Cursor has no fork/branch primitive, so forking would silently start a fresh session.
|
|
35
|
+
if (chat.provider === "cursor") return false
|
|
34
36
|
if (!chat.sessionToken && !chat.pendingForkSessionToken) return false
|
|
35
37
|
if (activeStatuses.has(chat.id)) return false
|
|
36
38
|
if (drainingChatIds.has(chat.id)) return false
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto"
|
|
2
|
+
import type { TranscriptEntry } from "../shared/types"
|
|
3
|
+
|
|
4
|
+
/** Stamp a transcript entry with a generated id and creation time. */
|
|
5
|
+
export function timestamped<T extends Omit<TranscriptEntry, "_id" | "createdAt">>(
|
|
6
|
+
entry: T,
|
|
7
|
+
createdAt = Date.now()
|
|
8
|
+
): TranscriptEntry {
|
|
9
|
+
return {
|
|
10
|
+
_id: randomUUID(),
|
|
11
|
+
createdAt,
|
|
12
|
+
...entry,
|
|
13
|
+
} as TranscriptEntry
|
|
14
|
+
}
|
|
@@ -102,6 +102,13 @@ const DEFAULT_APP_SETTINGS_SNAPSHOT: AppSettingsSnapshot = {
|
|
|
102
102
|
},
|
|
103
103
|
planMode: false,
|
|
104
104
|
},
|
|
105
|
+
cursor: {
|
|
106
|
+
model: "composer-2.5",
|
|
107
|
+
modelOptions: {
|
|
108
|
+
fastMode: false,
|
|
109
|
+
},
|
|
110
|
+
planMode: false,
|
|
111
|
+
},
|
|
105
112
|
},
|
|
106
113
|
warning: null,
|
|
107
114
|
filePathDisplay: "~/.kanna/data/settings.json",
|
package/src/server/ws-router.ts
CHANGED
|
@@ -15,7 +15,7 @@ import { EventStore } from "./event-store"
|
|
|
15
15
|
import { openExternal } from "./external-open"
|
|
16
16
|
import { KeybindingsManager } from "./keybindings"
|
|
17
17
|
import { killLocalHttpServer, listLocalHttpServers } from "./local-http-servers"
|
|
18
|
-
import { ensureProjectDirectory, resolveLocalPath } from "./paths"
|
|
18
|
+
import { cloneRepository, ensureProjectDirectory, resolveClonePath, resolveLocalPath } from "./paths"
|
|
19
19
|
import { readProjectQuickActions, writeProjectQuickActions } from "./project-quick-actions"
|
|
20
20
|
import { writeStandaloneTranscriptExport } from "./standalone-export"
|
|
21
21
|
import { TerminalManager } from "./terminal-manager"
|
|
@@ -473,9 +473,16 @@ export function createWsRouter({
|
|
|
473
473
|
planMode: false,
|
|
474
474
|
},
|
|
475
475
|
codex: {
|
|
476
|
-
model: "gpt-5.
|
|
476
|
+
model: "gpt-5.6-sol",
|
|
477
|
+
modelOptions: {
|
|
478
|
+
reasoningEffort: "medium",
|
|
479
|
+
fastMode: false,
|
|
480
|
+
},
|
|
481
|
+
planMode: false,
|
|
482
|
+
},
|
|
483
|
+
cursor: {
|
|
484
|
+
model: "composer-2.5",
|
|
477
485
|
modelOptions: {
|
|
478
|
-
reasoningEffort: "high",
|
|
479
486
|
fastMode: false,
|
|
480
487
|
},
|
|
481
488
|
planMode: false,
|
|
@@ -512,6 +519,14 @@ export function createWsRouter({
|
|
|
512
519
|
...patch.providerDefaults?.codex?.modelOptions,
|
|
513
520
|
},
|
|
514
521
|
},
|
|
522
|
+
cursor: {
|
|
523
|
+
...snapshot.providerDefaults.cursor,
|
|
524
|
+
...patch.providerDefaults?.cursor,
|
|
525
|
+
modelOptions: {
|
|
526
|
+
...snapshot.providerDefaults.cursor.modelOptions,
|
|
527
|
+
...patch.providerDefaults?.cursor?.modelOptions,
|
|
528
|
+
},
|
|
529
|
+
},
|
|
515
530
|
},
|
|
516
531
|
})
|
|
517
532
|
const resolvedAppSettings = {
|
|
@@ -1212,6 +1227,14 @@ export function createWsRouter({
|
|
|
1212
1227
|
await broadcastFilteredSnapshots({ includeSidebar: true })
|
|
1213
1228
|
return
|
|
1214
1229
|
}
|
|
1230
|
+
case "project.clone": {
|
|
1231
|
+
const cloneDest = await resolveClonePath(command.localPath, command.fallbackPath)
|
|
1232
|
+
await cloneRepository(command.cloneUrl, cloneDest)
|
|
1233
|
+
const project = await store.openProject(cloneDest, command.title)
|
|
1234
|
+
await refreshDiscovery()
|
|
1235
|
+
send(ws, { v: PROTOCOL_VERSION, type: "ack", id, result: { projectId: project.id, localPath: cloneDest } })
|
|
1236
|
+
break
|
|
1237
|
+
}
|
|
1215
1238
|
case "project.remove": {
|
|
1216
1239
|
await store.removeProject(command.projectId)
|
|
1217
1240
|
send(ws, { v: PROTOCOL_VERSION, type: "ack", id })
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Exhaustiveness guard for discriminated unions. Reaching this at runtime means
|
|
3
|
+
* a case was missed; referencing `value: never` makes it a compile-time error too.
|
|
4
|
+
*/
|
|
5
|
+
export function assertNever(value: never): never {
|
|
6
|
+
throw new Error(`Unexpected value: ${JSON.stringify(value)}`)
|
|
7
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Utilities for detecting and parsing GitHub/GitLab clone URLs.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const GIT_URL_PATTERNS = [
|
|
6
|
+
// HTTPS: https://github.com/owner/repo or https://github.com/owner/repo.git
|
|
7
|
+
/^https?:\/\/(github\.com|gitlab\.com)\/([^/]+)\/([^/.]+?)(?:\.git)?\/?$/,
|
|
8
|
+
// SSH: git@github.com:owner/repo.git
|
|
9
|
+
/^git@(github\.com|gitlab\.com):([^/]+)\/([^/.]+?)(?:\.git)?\/?$/,
|
|
10
|
+
]
|
|
11
|
+
|
|
12
|
+
export interface ParsedGitUrl {
|
|
13
|
+
host: string
|
|
14
|
+
owner: string
|
|
15
|
+
repo: string
|
|
16
|
+
url: string
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Check if a string looks like a GitHub or GitLab repository URL.
|
|
21
|
+
*/
|
|
22
|
+
export function isGitRepoUrl(input: string): boolean {
|
|
23
|
+
const trimmed = input.trim()
|
|
24
|
+
return GIT_URL_PATTERNS.some((pattern) => pattern.test(trimmed))
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Parse a GitHub/GitLab URL into its components.
|
|
29
|
+
* Returns null if the input isn't a valid git repo URL.
|
|
30
|
+
*/
|
|
31
|
+
export function parseGitRepoUrl(input: string): ParsedGitUrl | null {
|
|
32
|
+
const trimmed = input.trim()
|
|
33
|
+
for (const pattern of GIT_URL_PATTERNS) {
|
|
34
|
+
const match = trimmed.match(pattern)
|
|
35
|
+
if (match) {
|
|
36
|
+
return {
|
|
37
|
+
host: match[1]!,
|
|
38
|
+
owner: match[2]!,
|
|
39
|
+
repo: match[3]!,
|
|
40
|
+
url: trimmed,
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return null
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Normalize a git repo URL to HTTPS format for cloning.
|
|
49
|
+
*/
|
|
50
|
+
export function toCloneUrl(input: string): string {
|
|
51
|
+
const parsed = parseGitRepoUrl(input)
|
|
52
|
+
if (!parsed) return input.trim()
|
|
53
|
+
return `https://${parsed.host}/${parsed.owner}/${parsed.repo}.git`
|
|
54
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/** Small, dependency-free coercion helpers for parsing untrusted JSON values. */
|
|
2
|
+
|
|
3
|
+
export function asRecord(value: unknown): Record<string, unknown> | null {
|
|
4
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return null
|
|
5
|
+
return value as Record<string, unknown>
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function asString(value: unknown): string | undefined {
|
|
9
|
+
return typeof value === "string" ? value : undefined
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function asNumber(value: unknown): number | undefined {
|
|
13
|
+
return typeof value === "number" && Number.isFinite(value) ? value : undefined
|
|
14
|
+
}
|
package/src/shared/protocol.ts
CHANGED
|
@@ -73,6 +73,7 @@ export type ClientCommand =
|
|
|
73
73
|
| { type: "project.open"; localPath: string }
|
|
74
74
|
| { type: "project.create"; localPath: string; title: string }
|
|
75
75
|
| { type: "project.rename"; projectId: string; title: string }
|
|
76
|
+
| { type: "project.clone"; cloneUrl: string; localPath: string; fallbackPath?: string; title: string }
|
|
76
77
|
| { type: "project.remove"; projectId: string }
|
|
77
78
|
| { type: "sidebar.reorderProjectGroups"; projectIds: string[] }
|
|
78
79
|
| { type: "project.readDiffPatch"; projectId: string; path: string }
|
package/src/shared/types.test.ts
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import { describe, expect, test } from "bun:test"
|
|
2
2
|
import {
|
|
3
3
|
deriveClaudeModelLabel,
|
|
4
|
+
getCodexReasoningOptions,
|
|
4
5
|
normalizeClaudeModelId,
|
|
5
6
|
normalizeCodexModelId,
|
|
7
|
+
normalizeCodexReasoningEffort,
|
|
8
|
+
isCodexReasoningEffort,
|
|
6
9
|
supportsClaudeMaxReasoningEffort,
|
|
7
10
|
} from "./types"
|
|
8
11
|
|
|
@@ -21,8 +24,54 @@ describe("shared model normalization", () => {
|
|
|
21
24
|
})
|
|
22
25
|
|
|
23
26
|
test("normalizes legacy Codex aliases and defaults to the latest catalog model", () => {
|
|
24
|
-
expect(normalizeCodexModelId()).toBe("gpt-5.
|
|
27
|
+
expect(normalizeCodexModelId()).toBe("gpt-5.6-sol")
|
|
28
|
+
expect(normalizeCodexModelId("gpt-5.6")).toBe("gpt-5.6-sol")
|
|
29
|
+
expect(normalizeCodexModelId("gpt-5.6-terra")).toBe("gpt-5.6-terra")
|
|
30
|
+
expect(normalizeCodexModelId("gpt-5.6-luna")).toBe("gpt-5.6-luna")
|
|
25
31
|
expect(normalizeCodexModelId("gpt-5-codex")).toBe("gpt-5.3-codex")
|
|
32
|
+
expect(normalizeCodexModelId("not-a-real-model")).toBe("gpt-5.6-sol")
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
test("exposes model-specific GPT-5.6 reasoning efforts", () => {
|
|
36
|
+
expect(getCodexReasoningOptions("gpt-5.6-sol").map((option) => option.id)).toEqual([
|
|
37
|
+
"low", "medium", "high", "xhigh", "max", "ultra",
|
|
38
|
+
])
|
|
39
|
+
expect(getCodexReasoningOptions("gpt-5.6-terra").map((option) => option.id)).toEqual([
|
|
40
|
+
"low", "medium", "high", "xhigh", "max", "ultra",
|
|
41
|
+
])
|
|
42
|
+
expect(getCodexReasoningOptions("gpt-5.6-luna").map((option) => option.id)).toEqual([
|
|
43
|
+
"low", "medium", "high", "xhigh", "max",
|
|
44
|
+
])
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
test("preserves all 17 supported GPT-5.6 model and reasoning combinations", () => {
|
|
48
|
+
const combinations = [
|
|
49
|
+
["gpt-5.6-sol", ["low", "medium", "high", "xhigh", "max", "ultra"]],
|
|
50
|
+
["gpt-5.6-terra", ["low", "medium", "high", "xhigh", "max", "ultra"]],
|
|
51
|
+
["gpt-5.6-luna", ["low", "medium", "high", "xhigh", "max"]],
|
|
52
|
+
] as const
|
|
53
|
+
|
|
54
|
+
expect(combinations.reduce((count, [, efforts]) => count + efforts.length, 0)).toBe(17)
|
|
55
|
+
for (const [model, efforts] of combinations) {
|
|
56
|
+
for (const effort of efforts) {
|
|
57
|
+
expect(normalizeCodexReasoningEffort(model, effort)).toBe(effort)
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
test("normalizes unsupported GPT-5.6 reasoning efforts", () => {
|
|
63
|
+
expect(normalizeCodexReasoningEffort("gpt-5.6-sol", "minimal")).toBe("low")
|
|
64
|
+
expect(normalizeCodexReasoningEffort("gpt-5.6-luna", "ultra")).toBe("max")
|
|
65
|
+
expect(normalizeCodexReasoningEffort("gpt-5.6-terra", "ultra")).toBe("ultra")
|
|
66
|
+
expect(normalizeCodexReasoningEffort("gpt-5.6-sol", "unknown")).toBe("medium")
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
test("recognizes public and legacy Codex reasoning values", () => {
|
|
70
|
+
expect(isCodexReasoningEffort("max")).toBe(true)
|
|
71
|
+
expect(isCodexReasoningEffort("ultra")).toBe(true)
|
|
72
|
+
expect(isCodexReasoningEffort("minimal")).toBe(true)
|
|
73
|
+
expect(getCodexReasoningOptions("gpt-5.6-sol").find((option) => option.id === "xhigh")?.label).toBe("Extra High")
|
|
74
|
+
expect(getCodexReasoningOptions("gpt-5.6-sol").find((option) => option.id === "ultra")?.description).toContain("subagents")
|
|
26
75
|
})
|
|
27
76
|
|
|
28
77
|
test("uses declarative metadata for Claude max-effort support", () => {
|