dsh-code 1.0.0 → 1.0.2
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/README.en.md +278 -265
- package/README.md +277 -264
- package/cordis.patch.yml +7 -0
- package/lib/index.mjs +1649 -575
- package/lib/types/app.d.ts +19 -14
- package/lib/types/attachments.d.ts +14 -1
- package/lib/types/authorization-panel.d.ts +22 -0
- package/lib/types/authorization.d.ts +36 -0
- package/lib/types/keyboard.d.ts +32 -3
- package/lib/types/mentions.d.ts +4 -0
- package/lib/types/models.d.ts +3 -1
- package/lib/types/permissions.d.ts +4 -14
- package/lib/types/presets.d.ts +4 -17
- package/lib/types/render/animations.d.ts +27 -11
- package/lib/types/render/editor.d.ts +32 -7
- package/lib/types/render/lines.d.ts +1 -1
- package/lib/types/render/status.d.ts +1 -1
- package/package.json +49 -43
- package/src/app.ts +4570 -4188
- package/src/attachments.ts +93 -2
- package/src/authorization-panel.ts +285 -0
- package/src/authorization.ts +147 -0
- package/src/index.ts +34 -21
- package/src/internals.ts +26 -9
- package/src/keyboard.ts +164 -39
- package/src/mentions.ts +12 -1
- package/src/models.ts +20 -14
- package/src/permissions.ts +5 -13
- package/src/presets.ts +5 -18
- package/src/provider-settings.ts +1 -1
- package/src/render/animations.ts +447 -400
- package/src/render/editor.ts +125 -25
- package/src/render/lines.ts +16 -2
- package/src/render/projection.ts +7 -3
- package/src/render/status.ts +2 -2
- package/src/session-directory.ts +1 -1
package/src/internals.ts
CHANGED
|
@@ -8,7 +8,16 @@
|
|
|
8
8
|
|
|
9
9
|
import { render } from 'ink'
|
|
10
10
|
import type { ReactElement } from 'react'
|
|
11
|
-
import {
|
|
11
|
+
import {
|
|
12
|
+
BRACKETED_PASTE_DISABLE,
|
|
13
|
+
BRACKETED_PASTE_ENABLE,
|
|
14
|
+
KEYBOARD_ENHANCE_DISABLE,
|
|
15
|
+
KEYBOARD_ENHANCE_ENABLE,
|
|
16
|
+
TERMINAL_FOCUS_REPORT_DISABLE,
|
|
17
|
+
TERMINAL_FOCUS_REPORT_ENABLE,
|
|
18
|
+
isVsCodeTerminalEnv,
|
|
19
|
+
shouldEnableKeyboardEnhancement,
|
|
20
|
+
} from './keyboard.ts'
|
|
12
21
|
|
|
13
22
|
/** A mounted terminal app instance; the runner owns unmount ordering. */
|
|
14
23
|
export interface TuiMount {
|
|
@@ -29,11 +38,16 @@ export const internals: {
|
|
|
29
38
|
stderr: { write(chunk: string): unknown }
|
|
30
39
|
} = {
|
|
31
40
|
mount: (element: ReactElement): TuiMount => {
|
|
32
|
-
//
|
|
33
|
-
//
|
|
34
|
-
//
|
|
35
|
-
|
|
36
|
-
|
|
41
|
+
// VS Code's integrated terminal can route Tab to the workbench when Kitty
|
|
42
|
+
// enhancement is enabled. Keep bracketed paste everywhere, but only push
|
|
43
|
+
// the keyboard protocol on terminals that can safely own those key events.
|
|
44
|
+
const keyboardEnhanced = shouldEnableKeyboardEnhancement()
|
|
45
|
+
const focusReporting = isVsCodeTerminalEnv()
|
|
46
|
+
process.stdout.write(
|
|
47
|
+
(keyboardEnhanced ? KEYBOARD_ENHANCE_ENABLE : '')
|
|
48
|
+
+ BRACKETED_PASTE_ENABLE
|
|
49
|
+
+ (focusReporting ? TERMINAL_FOCUS_REPORT_ENABLE : ''),
|
|
50
|
+
)
|
|
37
51
|
// App owns Ctrl+C's deliberate three-state contract (interrupt, clear
|
|
38
52
|
// draft, quit). Ink's default `exitOnCtrlC: true` would intercept the
|
|
39
53
|
// normalized control byte first, unmount only its renderer, and leave the
|
|
@@ -45,9 +59,12 @@ export const internals: {
|
|
|
45
59
|
},
|
|
46
60
|
unmount(): void {
|
|
47
61
|
instance.unmount()
|
|
48
|
-
// Pop
|
|
49
|
-
|
|
50
|
-
|
|
62
|
+
// Pop only a stack this mount pushed, then disable bracketed paste.
|
|
63
|
+
process.stdout.write(
|
|
64
|
+
(keyboardEnhanced ? KEYBOARD_ENHANCE_DISABLE : '')
|
|
65
|
+
+ BRACKETED_PASTE_DISABLE
|
|
66
|
+
+ (focusReporting ? TERMINAL_FOCUS_REPORT_DISABLE : ''),
|
|
67
|
+
)
|
|
51
68
|
},
|
|
52
69
|
}
|
|
53
70
|
},
|
package/src/keyboard.ts
CHANGED
|
@@ -2,33 +2,74 @@
|
|
|
2
2
|
* Keyboard enhancement protocol (Codex `keyboard_modes` parity) and the
|
|
3
3
|
* kitty CSI-u normalization layer.
|
|
4
4
|
*
|
|
5
|
-
* The TUI pushes the kitty keyboard protocol with DISAMBIGUATE_ESCAPE_CODES
|
|
6
|
-
* and REPORT_ALTERNATE_KEYS (flags 1|4 = `\x1b[>5u`)
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* nothing.
|
|
5
|
+
* The TUI pushes the kitty keyboard protocol with DISAMBIGUATE_ESCAPE_CODES
|
|
6
|
+
* and REPORT_ALTERNATE_KEYS (flags 1|4 = `\x1b[>5u`). Event types are
|
|
7
|
+
* deliberately NOT requested: Ink 5's parser cannot decode the
|
|
8
|
+
* `:event-type` suffix, and repeat/release reporting buys this surface
|
|
9
|
+
* nothing.
|
|
11
10
|
*
|
|
12
11
|
* Ink 5 also cannot parse most CSI-u forms at all — they fall through its
|
|
13
12
|
* regex as unnamed sequences and get INSERTED AS DRAFT TEXT. The composer's
|
|
14
13
|
* stdin read patch therefore rewrites every CSI-u form it can decode back
|
|
15
14
|
* to the legacy byte or canonical sequence the existing key handling
|
|
16
|
-
* already understands, before Ink ever parses the chunk.
|
|
17
|
-
*
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
export const KEYBOARD_ENHANCE_ENABLE = '\x1b[>4;0m\x1b[>5u'
|
|
15
|
+
* already understands, before Ink ever parses the chunk.
|
|
16
|
+
* @module @deepseek-ai/dsh-code/keyboard
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
/** Push keyboard enhancement (modifyOtherKeys off, kitty flags 1|4). */
|
|
20
|
+
export const KEYBOARD_ENHANCE_ENABLE = '\x1b[>4;0m\x1b[>5u'
|
|
23
21
|
|
|
24
22
|
/** Pop the enhancement stack and reset modifyOtherKeys (exit path). */
|
|
25
|
-
export const KEYBOARD_ENHANCE_DISABLE = '\x1b[<u\x1b[>4;0m'
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
export const
|
|
23
|
+
export const KEYBOARD_ENHANCE_DISABLE = '\x1b[<u\x1b[>4;0m'
|
|
24
|
+
|
|
25
|
+
/** Explicit environment overrides for terminal keyboard enhancement. */
|
|
26
|
+
export const DSH_DISABLE_KEYBOARD_ENHANCEMENT = 'DSH_DISABLE_KEYBOARD_ENHANCEMENT'
|
|
27
|
+
export const DSH_ENABLE_KEYBOARD_ENHANCEMENT = 'DSH_ENABLE_KEYBOARD_ENHANCEMENT'
|
|
28
|
+
|
|
29
|
+
function parseBooleanEnv(value: string | undefined): boolean | undefined {
|
|
30
|
+
if (value === undefined) return undefined
|
|
31
|
+
const normalized = value.trim().toLowerCase()
|
|
32
|
+
if (normalized === '1' || normalized === 'true' || normalized === 'yes') return true
|
|
33
|
+
if (normalized === '0' || normalized === 'false' || normalized === 'no') return false
|
|
34
|
+
return undefined
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** True when the process is running inside the VS Code integrated terminal. */
|
|
38
|
+
export function isVsCodeTerminalEnv(env: NodeJS.ProcessEnv = process.env): boolean {
|
|
39
|
+
return env.TERM_PROGRAM?.trim().toLowerCase() === 'vscode' || env.VSCODE_INJECTION === '1'
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** Whether to push Kitty keyboard enhancement for the current terminal. */
|
|
43
|
+
export function shouldEnableKeyboardEnhancement(env: NodeJS.ProcessEnv = process.env): boolean {
|
|
44
|
+
const explicitDisable = parseBooleanEnv(env[DSH_DISABLE_KEYBOARD_ENHANCEMENT])
|
|
45
|
+
if (explicitDisable === true) return false
|
|
46
|
+
const explicitEnable = parseBooleanEnv(env[DSH_ENABLE_KEYBOARD_ENHANCEMENT])
|
|
47
|
+
if (explicitEnable !== undefined) return explicitEnable
|
|
48
|
+
return !isVsCodeTerminalEnv(env)
|
|
49
|
+
}
|
|
29
50
|
|
|
30
|
-
/**
|
|
31
|
-
export const
|
|
51
|
+
/** Enable bracketed paste reporting. */
|
|
52
|
+
export const BRACKETED_PASTE_ENABLE = '\x1b[?2004h'
|
|
53
|
+
|
|
54
|
+
/** Disable bracketed paste reporting. */
|
|
55
|
+
export const BRACKETED_PASTE_DISABLE = '\x1b[?2004l'
|
|
56
|
+
|
|
57
|
+
/** Enable terminal focus-in/focus-out reporting (xterm focus protocol). */
|
|
58
|
+
export const TERMINAL_FOCUS_REPORT_ENABLE = '\x1b[?1004h'
|
|
59
|
+
|
|
60
|
+
/** Disable terminal focus-in/focus-out reporting. */
|
|
61
|
+
export const TERMINAL_FOCUS_REPORT_DISABLE = '\x1b[?1004l'
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Remove xterm focus reports from one input chunk and update the caller's
|
|
65
|
+
* focus state. Focus reports are terminal protocol, not composer text.
|
|
66
|
+
*/
|
|
67
|
+
export function stripTerminalFocusEvents(chunk: string, onFocus: (focused: boolean) => void): string {
|
|
68
|
+
return chunk.replace(/\x1b\[(I|O)/gu, (_whole, event: string) => {
|
|
69
|
+
onFocus(event === 'I')
|
|
70
|
+
return ''
|
|
71
|
+
})
|
|
72
|
+
}
|
|
32
73
|
|
|
33
74
|
/** Bracketed paste markers as Ink delivers them (it strips the leading ESC). */
|
|
34
75
|
export const PASTE_START_MARKER = '[200~'
|
|
@@ -39,9 +80,13 @@ export const PASTE_END_MARKER = '[201~'
|
|
|
39
80
|
* `input` text, where an unhandled paste would otherwise persist the literal
|
|
40
81
|
* "[200~"/"[201~" markers Ink leaves after stripping the ESC byte.
|
|
41
82
|
*/
|
|
42
|
-
export function stripPasteMarkers(text: string): string {
|
|
43
|
-
return text
|
|
44
|
-
}
|
|
83
|
+
export function stripPasteMarkers(text: string): string {
|
|
84
|
+
return text
|
|
85
|
+
.replaceAll(`\x1b${PASTE_START_MARKER}`, '')
|
|
86
|
+
.replaceAll(`\x1b${PASTE_END_MARKER}`, '')
|
|
87
|
+
.replaceAll(PASTE_START_MARKER, '')
|
|
88
|
+
.replaceAll(PASTE_END_MARKER, '')
|
|
89
|
+
}
|
|
45
90
|
|
|
46
91
|
/** One decoded CSI-u keypress: key code, 1-based modifier param, alternate code. */
|
|
47
92
|
interface CsiUKey {
|
|
@@ -51,21 +96,20 @@ interface CsiUKey {
|
|
|
51
96
|
}
|
|
52
97
|
|
|
53
98
|
/** Match one CSI-u sequence (code, optional ;modifiers, then :event or ;alternate). */
|
|
54
|
-
const CSI_U_SOURCE = '\x1b\\[(\\d+)(?:;(\\d+))?(?:[:;](\\d+))?u'
|
|
55
|
-
|
|
56
|
-
/** Legacy equivalent for one decoded CSI-u key, or undefined to pass through. */
|
|
57
|
-
function legacyForKey(key: CsiUKey): string | undefined {
|
|
99
|
+
const CSI_U_SOURCE = '\x1b\\[(\\d+)(?:;(\\d+))?(?:[:;](\\d+))?u'
|
|
100
|
+
|
|
101
|
+
/** Legacy equivalent for one decoded CSI-u key, or undefined to pass through. */
|
|
102
|
+
function legacyForKey(key: CsiUKey): string | undefined {
|
|
58
103
|
const bits = Math.max(0, key.modifiers - 1)
|
|
59
104
|
const shift = (bits & 1) !== 0
|
|
60
105
|
const alt = (bits & 2) !== 0
|
|
61
|
-
const ctrl = (bits & 4) !== 0
|
|
62
|
-
if (key.code === 13) {
|
|
63
|
-
// Enter
|
|
64
|
-
//
|
|
65
|
-
if (
|
|
66
|
-
if (
|
|
67
|
-
|
|
68
|
-
return '\r'
|
|
106
|
+
const ctrl = (bits & 4) !== 0
|
|
107
|
+
if (key.code === 13) {
|
|
108
|
+
// Modified Enter has no dedicated composer behavior. Preserve the legacy
|
|
109
|
+
// Ctrl/Alt bytes and collapse every other form to ordinary Enter.
|
|
110
|
+
if (ctrl) return '\n'
|
|
111
|
+
if (alt) return '\x1b\r'
|
|
112
|
+
return '\r'
|
|
69
113
|
}
|
|
70
114
|
if (key.code === 27) return '\x1b'
|
|
71
115
|
if (key.code === 9) return shift ? '\x1b[Z' : '\t'
|
|
@@ -111,10 +155,10 @@ function legacyForKey(key: CsiUKey): string | undefined {
|
|
|
111
155
|
* sequences pass through untouched, so terminals without the protocol are
|
|
112
156
|
* unaffected.
|
|
113
157
|
*/
|
|
114
|
-
export function normalizeKeyboardChunk(chunk: string): string {
|
|
115
|
-
if (!chunk.includes('\x1b[') || !chunk.includes('u')) return chunk
|
|
116
|
-
const pattern = new RegExp(CSI_U_SOURCE, 'g')
|
|
117
|
-
return chunk.replace(pattern, (whole, code: string, mods?: string, third?: string) => {
|
|
158
|
+
export function normalizeKeyboardChunk(chunk: string): string {
|
|
159
|
+
if (!chunk.includes('\x1b[') || !chunk.includes('u')) return chunk
|
|
160
|
+
const pattern = new RegExp(CSI_U_SOURCE, 'g')
|
|
161
|
+
return chunk.replace(pattern, (whole, code: string, mods?: string, third?: string) => {
|
|
118
162
|
const legacy = legacyForKey({
|
|
119
163
|
code: Number.parseInt(code, 10),
|
|
120
164
|
modifiers: mods === undefined || mods === '' ? 1 : Math.max(1, Number.parseInt(mods, 10)),
|
|
@@ -122,4 +166,85 @@ export function normalizeKeyboardChunk(chunk: string): string {
|
|
|
122
166
|
})
|
|
123
167
|
return legacy ?? whole
|
|
124
168
|
})
|
|
125
|
-
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/** Editor actions Ink cannot distinguish reliably when terminal bytes batch. */
|
|
172
|
+
export type RawEditorToken =
|
|
173
|
+
| { readonly kind: 'text'; readonly text: string }
|
|
174
|
+
| { readonly kind: 'home' | 'end' | 'delete-backward' | 'delete-word-backward' | 'delete-forward' | 'delete-word-forward' }
|
|
175
|
+
|
|
176
|
+
const HOME_SEQUENCES = ['\x1b[H', '\x1b[1~', '\x1b[7~', '\x1bOH'] as const
|
|
177
|
+
const END_SEQUENCES = ['\x1b[F', '\x1b[4~', '\x1b[8~', '\x1bOF'] as const
|
|
178
|
+
|
|
179
|
+
/** Parse one CSI functional-key sequence at `offset`. */
|
|
180
|
+
function functionalToken(chunk: string, offset: number): { token: RawEditorToken; length: number } | undefined {
|
|
181
|
+
const tail = chunk.slice(offset)
|
|
182
|
+
for (const sequence of HOME_SEQUENCES) {
|
|
183
|
+
if (tail.startsWith(sequence)) return { token: { kind: 'home' }, length: sequence.length }
|
|
184
|
+
}
|
|
185
|
+
for (const sequence of END_SEQUENCES) {
|
|
186
|
+
if (tail.startsWith(sequence)) return { token: { kind: 'end' }, length: sequence.length }
|
|
187
|
+
}
|
|
188
|
+
const modifiedHome = /^\x1b\[1;(\d+)H/u.exec(tail)
|
|
189
|
+
if (modifiedHome !== null) return { token: { kind: 'home' }, length: modifiedHome[0].length }
|
|
190
|
+
const modifiedEnd = /^\x1b\[1;(\d+)F/u.exec(tail)
|
|
191
|
+
if (modifiedEnd !== null) return { token: { kind: 'end' }, length: modifiedEnd[0].length }
|
|
192
|
+
const modifiedDelete = /^\x1b\[3(?:;(\d+))?~/u.exec(tail)
|
|
193
|
+
if (modifiedDelete !== null) {
|
|
194
|
+
const modifiers = Number.parseInt(modifiedDelete[1] ?? '1', 10) - 1
|
|
195
|
+
const byWord = (modifiers & 2) !== 0 || (modifiers & 4) !== 0
|
|
196
|
+
return {
|
|
197
|
+
token: { kind: byWord ? 'delete-word-forward' : 'delete-forward' },
|
|
198
|
+
length: modifiedDelete[0].length,
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
return undefined
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Tokenize a stdin chunk containing at least one editor-only key. Ink calls
|
|
206
|
+
* `useInput` once for a pasted/batched chunk, so preserving each action here
|
|
207
|
+
* prevents repeated Backspace/Home/End/Delete presses from collapsing into
|
|
208
|
+
* one blurred key event. Unknown escape sequences return undefined and stay
|
|
209
|
+
* under Ink's ownership.
|
|
210
|
+
*/
|
|
211
|
+
export function tokenizeRawEditorChunk(chunk: string): readonly RawEditorToken[] | undefined {
|
|
212
|
+
const tokens: RawEditorToken[] = []
|
|
213
|
+
let text = ''
|
|
214
|
+
let special = false
|
|
215
|
+
const flushText = (): void => {
|
|
216
|
+
if (text === '') return
|
|
217
|
+
tokens.push({ kind: 'text', text })
|
|
218
|
+
text = ''
|
|
219
|
+
}
|
|
220
|
+
for (let offset = 0; offset < chunk.length;) {
|
|
221
|
+
if (chunk.startsWith('\x1b\x7f', offset) || chunk.startsWith('\x1b\b', offset)) {
|
|
222
|
+
flushText()
|
|
223
|
+
tokens.push({ kind: 'delete-word-backward' })
|
|
224
|
+
special = true
|
|
225
|
+
offset += 2
|
|
226
|
+
continue
|
|
227
|
+
}
|
|
228
|
+
const functional = functionalToken(chunk, offset)
|
|
229
|
+
if (functional !== undefined) {
|
|
230
|
+
flushText()
|
|
231
|
+
tokens.push(functional.token)
|
|
232
|
+
special = true
|
|
233
|
+
offset += functional.length
|
|
234
|
+
continue
|
|
235
|
+
}
|
|
236
|
+
const char = chunk[offset]!
|
|
237
|
+
if (char === '\x7f' || char === '\b') {
|
|
238
|
+
flushText()
|
|
239
|
+
tokens.push({ kind: 'delete-backward' })
|
|
240
|
+
special = true
|
|
241
|
+
offset += 1
|
|
242
|
+
continue
|
|
243
|
+
}
|
|
244
|
+
if (char === '\x1b') return undefined
|
|
245
|
+
text += char
|
|
246
|
+
offset += 1
|
|
247
|
+
}
|
|
248
|
+
flushText()
|
|
249
|
+
return special ? tokens : undefined
|
|
250
|
+
}
|
package/src/mentions.ts
CHANGED
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
|
|
23
23
|
import type { Context } from '@deepseek-ai/cordis'
|
|
24
24
|
import type { Agent } from '@deepseek-ai/dsh-agent'
|
|
25
|
+
import { isAbsolute, resolve } from 'node:path'
|
|
25
26
|
import {
|
|
26
27
|
DEFAULT_FILE_SEARCH_EXCLUDED_DIRECTORIES,
|
|
27
28
|
DEFAULT_FILE_SEARCH_MAX_ENTRIES,
|
|
@@ -46,6 +47,8 @@ export interface MentionCandidate {
|
|
|
46
47
|
description: string
|
|
47
48
|
/** Origin kind for icon/coloring decisions. */
|
|
48
49
|
kind: 'file' | 'directory' | 'session'
|
|
50
|
+
/** Absolute path for file candidates; never rendered or persisted directly. */
|
|
51
|
+
path?: string
|
|
49
52
|
}
|
|
50
53
|
|
|
51
54
|
/** Prepared submission: readable content plus optional injected context. */
|
|
@@ -72,6 +75,11 @@ interface FileReferenceServiceLike {
|
|
|
72
75
|
/** Menu cap on file rows; the service owns ranking and default rows. */
|
|
73
76
|
const MAX_FILE_ROWS = 20
|
|
74
77
|
|
|
78
|
+
/** Whether a mention token is already navigating a filesystem path. */
|
|
79
|
+
export function isPathLikeMentionQuery(query: string): boolean {
|
|
80
|
+
return /[\\/]/u.test(query)
|
|
81
|
+
}
|
|
82
|
+
|
|
75
83
|
/** The mention API the input editor and the runner share. */
|
|
76
84
|
export interface MentionsApi {
|
|
77
85
|
/** Ranked menu candidates for the typed `@` query. */
|
|
@@ -133,7 +141,7 @@ export function createMentions(ctx: Context, agent: Agent | undefined, cwd: stri
|
|
|
133
141
|
: agent === undefined
|
|
134
142
|
? preSessionFiles(needle, signal).catch(() => [] as readonly ServiceFileCandidate[])
|
|
135
143
|
: Promise.resolve([] as readonly ServiceFileCandidate[]),
|
|
136
|
-
sessionCapable && needle !== '' && agent !== undefined
|
|
144
|
+
sessionCapable && needle !== '' && !isPathLikeMentionQuery(needle) && agent !== undefined
|
|
137
145
|
? resolver!.listCandidates(agent, needle, 10, signal).catch(() => [] as readonly SessionReferenceCandidate[])
|
|
138
146
|
: Promise.resolve([] as readonly SessionReferenceCandidate[]),
|
|
139
147
|
])
|
|
@@ -144,6 +152,9 @@ export function createMentions(ctx: Context, agent: Agent | undefined, cwd: stri
|
|
|
144
152
|
label: candidate.path,
|
|
145
153
|
description: candidate.kind === 'directory' ? 'Folder' : 'File',
|
|
146
154
|
kind: candidate.kind,
|
|
155
|
+
...candidate.kind === 'file'
|
|
156
|
+
? { path: isAbsolute(candidate.path) ? candidate.path : resolve(cwd, candidate.path) }
|
|
157
|
+
: {},
|
|
147
158
|
}))
|
|
148
159
|
const sessionRows: MentionCandidate[] = sessions.map(candidate => ({
|
|
149
160
|
label: formatSessionReferenceMention(candidate),
|
package/src/models.ts
CHANGED
|
@@ -13,10 +13,11 @@ import type { ModelSelection } from '@deepseek-ai/dsh-agent'
|
|
|
13
13
|
import {
|
|
14
14
|
ReasoningEffortId,
|
|
15
15
|
type LlmCallConfig,
|
|
16
|
-
type LlmModelInfo,
|
|
17
|
-
type LlmModelReasoningInfo,
|
|
18
|
-
type LlmResolvedModelInfo,
|
|
19
|
-
|
|
16
|
+
type LlmModelInfo,
|
|
17
|
+
type LlmModelReasoningInfo,
|
|
18
|
+
type LlmResolvedModelInfo,
|
|
19
|
+
type ModelModality,
|
|
20
|
+
} from '@deepseek-ai/dsh-llm'
|
|
20
21
|
|
|
21
22
|
/** Display metadata for one adapter-owned reasoning effort (mirrors `LlmReasoningEffortInfo`). */
|
|
22
23
|
export interface ModelReasoningEffort {
|
|
@@ -44,9 +45,11 @@ export interface ModelRow {
|
|
|
44
45
|
providerName: string
|
|
45
46
|
/** Provider-owned model id. */
|
|
46
47
|
model: string
|
|
47
|
-
/** Human-readable model name. */
|
|
48
|
-
modelName: string
|
|
49
|
-
/**
|
|
48
|
+
/** Human-readable model name. */
|
|
49
|
+
modelName: string
|
|
50
|
+
/** Request modalities advertised for this exact route; absent means unknown. */
|
|
51
|
+
inputModalities?: readonly ModelModality[]
|
|
52
|
+
/** Adapter-owned selectable reasoning levels when the model exposes any. */
|
|
50
53
|
reasoning?: ModelReasoning
|
|
51
54
|
}
|
|
52
55
|
|
|
@@ -194,16 +197,19 @@ export async function loadModelDirectory(ctx: Context): Promise<ModelDirectory>
|
|
|
194
197
|
const rows = await Promise.all(models.map(async (model): Promise<ModelRow> => {
|
|
195
198
|
const row: ModelRow = {
|
|
196
199
|
provider: provider.id,
|
|
197
|
-
providerName: provider.name,
|
|
198
|
-
model: model.id,
|
|
199
|
-
modelName: model.name,
|
|
200
|
-
|
|
200
|
+
providerName: provider.name,
|
|
201
|
+
model: model.id,
|
|
202
|
+
modelName: model.name,
|
|
203
|
+
...model.inputModalities === undefined ? {} : { inputModalities: [...model.inputModalities] },
|
|
204
|
+
}
|
|
201
205
|
if (llmResolve.resolveModelInfo === undefined) return row
|
|
202
206
|
try {
|
|
203
207
|
const resolved = await llmResolve.resolveModelInfo(provider.id, model.id)
|
|
204
|
-
return
|
|
205
|
-
|
|
206
|
-
: {
|
|
208
|
+
return {
|
|
209
|
+
...row,
|
|
210
|
+
...resolved.inputModalities === undefined ? {} : { inputModalities: [...resolved.inputModalities] },
|
|
211
|
+
...resolved.reasoning === undefined ? {} : { reasoning: mapReasoning(resolved.reasoning) },
|
|
212
|
+
}
|
|
207
213
|
} catch {
|
|
208
214
|
reasoningFailures.push(`${provider.id}/${model.id}`)
|
|
209
215
|
return row
|
package/src/permissions.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
/** Permission-preset policy for pending and active TUI sessions. */
|
|
2
2
|
|
|
3
3
|
import type { Context } from '@deepseek-ai/cordis'
|
|
4
|
-
import type {
|
|
4
|
+
import type { PermissionPresetService } from '@deepseek-ai/dsh-permission-presets'
|
|
5
|
+
import type { Session } from '@deepseek-ai/dsh-session'
|
|
5
6
|
|
|
6
7
|
/** One selectable permission preset row for the /permission panel. */
|
|
7
8
|
export interface PermissionRow {
|
|
@@ -9,20 +10,12 @@ export interface PermissionRow {
|
|
|
9
10
|
readonly description?: string
|
|
10
11
|
}
|
|
11
12
|
|
|
12
|
-
/**
|
|
13
|
-
export
|
|
14
|
-
readonly names: readonly string[]
|
|
15
|
-
readonly defaultPreset: string
|
|
16
|
-
resolve(name: string): unknown
|
|
17
|
-
current(events: readonly SessionEvent[]): string
|
|
18
|
-
set(session: Session, preset: string): void
|
|
19
|
-
/** Client presentation metadata for one preset; may reject unknown names. */
|
|
20
|
-
optionOf?(name: string): { name: string; description?: string } | undefined
|
|
21
|
-
}
|
|
13
|
+
/** Public compatibility alias for the official upstream permission service. */
|
|
14
|
+
export type PermissionPresetsService = PermissionPresetService
|
|
22
15
|
|
|
23
16
|
/** Read the optional Harness service without importing its runtime package. */
|
|
24
17
|
export function permissionPresetsFrom(ctx: Context): PermissionPresetsService | undefined {
|
|
25
|
-
return
|
|
18
|
+
return ctx.get('permissionPresets')
|
|
26
19
|
}
|
|
27
20
|
|
|
28
21
|
/** Effective label for either an active session or the not-yet-created first one. */
|
|
@@ -75,7 +68,6 @@ export function applyPendingPermission(
|
|
|
75
68
|
*/
|
|
76
69
|
export function listPermissionRows(service: PermissionPresetsService): readonly PermissionRow[] {
|
|
77
70
|
return service.names.map((id) => {
|
|
78
|
-
if (service.optionOf === undefined) return { id }
|
|
79
71
|
try {
|
|
80
72
|
return { id, description: service.optionOf(id)?.description }
|
|
81
73
|
} catch {
|
package/src/presets.ts
CHANGED
|
@@ -2,31 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
import type { Context } from '@deepseek-ai/cordis'
|
|
4
4
|
import type { Agent } from '@deepseek-ai/dsh-agent'
|
|
5
|
+
import type { AgentPreset, AgentPresets } from '@deepseek-ai/dsh-agent-presets'
|
|
5
6
|
import type { Session, SessionEvent } from '@deepseek-ai/dsh-session'
|
|
6
7
|
|
|
7
8
|
/** One discoverable agent composition. */
|
|
8
|
-
export
|
|
9
|
-
readonly id: string
|
|
10
|
-
readonly trust: 'system' | 'user'
|
|
11
|
-
readonly name?: string
|
|
12
|
-
readonly description?: string
|
|
13
|
-
readonly order?: number
|
|
14
|
-
readonly broken?: string
|
|
15
|
-
}
|
|
9
|
+
export type PresetRow = AgentPreset
|
|
16
10
|
|
|
17
|
-
/**
|
|
18
|
-
export
|
|
19
|
-
readonly defaultId: string
|
|
20
|
-
list(): Promise<PresetRow[]>
|
|
21
|
-
resolve(id?: string): Promise<PresetRow>
|
|
22
|
-
mount(agentCtx: Context, id?: string): Promise<PresetRow>
|
|
23
|
-
recompose(agentCtx: Context, id: string): Promise<PresetRow>
|
|
24
|
-
composedPreset(agentCtx: Context): string | undefined
|
|
25
|
-
}
|
|
11
|
+
/** Public compatibility alias for the official upstream service type. */
|
|
12
|
+
export type AgentPresetsService = AgentPresets
|
|
26
13
|
|
|
27
14
|
/** Read an optional Cordis service without requiring its package at build time. */
|
|
28
15
|
export function agentPresetsFrom(ctx: Context): AgentPresetsService | undefined {
|
|
29
|
-
return
|
|
16
|
+
return ctx.get('agentPresets')
|
|
30
17
|
}
|
|
31
18
|
|
|
32
19
|
/** A preset may change only before the first durable turn begins. */
|
package/src/provider-settings.ts
CHANGED
|
@@ -249,7 +249,7 @@ export interface ProviderSettingsDirectory {
|
|
|
249
249
|
|
|
250
250
|
/** Events that invalidate the official Models provider/settings/credential join. */
|
|
251
251
|
const PROVIDER_SETTINGS_EVENTS = [
|
|
252
|
-
'credentials/updated',
|
|
252
|
+
'credentials/reference-updated',
|
|
253
253
|
'settings/document-updated',
|
|
254
254
|
'llm/adapters-updated',
|
|
255
255
|
] as const
|