dsh-code 1.0.0 → 1.0.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/src/keyboard.ts CHANGED
@@ -2,24 +2,22 @@
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`), which makes terminals
7
- * report Shift+Enter as `CSI 13;2u` instead of a bare CR. Event types are
8
- * deliberately NOT requested: Ink 5's parser cannot decode the
9
- * `:event-type` suffix, and repeat/release reporting buys this surface
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
- * @module @deepseek-ai/dsh-code/keyboard
19
- */
20
-
21
- /** Push keyboard enhancement (modifyOtherKeys off, kitty flags 1|4). */
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
23
  export const KEYBOARD_ENHANCE_DISABLE = '\x1b[<u\x1b[>4;0m'
@@ -39,9 +37,13 @@ export const PASTE_END_MARKER = '[201~'
39
37
  * `input` text, where an unhandled paste would otherwise persist the literal
40
38
  * "[200~"/"[201~" markers Ink leaves after stripping the ESC byte.
41
39
  */
42
- export function stripPasteMarkers(text: string): string {
43
- return text.replaceAll(PASTE_START_MARKER, '').replaceAll(PASTE_END_MARKER, '')
44
- }
40
+ export function stripPasteMarkers(text: string): string {
41
+ return text
42
+ .replaceAll(`\x1b${PASTE_START_MARKER}`, '')
43
+ .replaceAll(`\x1b${PASTE_END_MARKER}`, '')
44
+ .replaceAll(PASTE_START_MARKER, '')
45
+ .replaceAll(PASTE_END_MARKER, '')
46
+ }
45
47
 
46
48
  /** One decoded CSI-u keypress: key code, 1-based modifier param, alternate code. */
47
49
  interface CsiUKey {
@@ -51,21 +53,20 @@ interface CsiUKey {
51
53
  }
52
54
 
53
55
  /** 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 {
56
+ const CSI_U_SOURCE = '\x1b\\[(\\d+)(?:;(\\d+))?(?:[:;](\\d+))?u'
57
+
58
+ /** Legacy equivalent for one decoded CSI-u key, or undefined to pass through. */
59
+ function legacyForKey(key: CsiUKey): string | undefined {
58
60
  const bits = Math.max(0, key.modifiers - 1)
59
61
  const shift = (bits & 1) !== 0
60
62
  const alt = (bits & 2) !== 0
61
- const ctrl = (bits & 4) !== 0
62
- if (key.code === 13) {
63
- // Enter: shift keeps the canonical enhanced form the composer detects;
64
- // ctrl maps to LF (the Ctrl+J newline binding), alt to ESC+CR.
65
- if (shift) return '\x1b[13;2u'
66
- if (ctrl) return '\n'
67
- if (alt) return '\x1b\r'
68
- return '\r'
63
+ const ctrl = (bits & 4) !== 0
64
+ if (key.code === 13) {
65
+ // Modified Enter has no dedicated composer behavior. Preserve the legacy
66
+ // Ctrl/Alt bytes and collapse every other form to ordinary Enter.
67
+ if (ctrl) return '\n'
68
+ if (alt) return '\x1b\r'
69
+ return '\r'
69
70
  }
70
71
  if (key.code === 27) return '\x1b'
71
72
  if (key.code === 9) return shift ? '\x1b[Z' : '\t'
@@ -111,10 +112,10 @@ function legacyForKey(key: CsiUKey): string | undefined {
111
112
  * sequences pass through untouched, so terminals without the protocol are
112
113
  * unaffected.
113
114
  */
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) => {
115
+ export function normalizeKeyboardChunk(chunk: string): string {
116
+ if (!chunk.includes('\x1b[') || !chunk.includes('u')) return chunk
117
+ const pattern = new RegExp(CSI_U_SOURCE, 'g')
118
+ return chunk.replace(pattern, (whole, code: string, mods?: string, third?: string) => {
118
119
  const legacy = legacyForKey({
119
120
  code: Number.parseInt(code, 10),
120
121
  modifiers: mods === undefined || mods === '' ? 1 : Math.max(1, Number.parseInt(mods, 10)),
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. */
@@ -144,6 +147,9 @@ export function createMentions(ctx: Context, agent: Agent | undefined, cwd: stri
144
147
  label: candidate.path,
145
148
  description: candidate.kind === 'directory' ? 'Folder' : 'File',
146
149
  kind: candidate.kind,
150
+ ...candidate.kind === 'file'
151
+ ? { path: isAbsolute(candidate.path) ? candidate.path : resolve(cwd, candidate.path) }
152
+ : {},
147
153
  }))
148
154
  const sessionRows: MentionCandidate[] = sessions.map(candidate => ({
149
155
  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
- } from '@deepseek-ai/dsh-llm'
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
- /** Adapter-owned selectable reasoning levels when the model exposes any. */
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 resolved.reasoning === undefined
205
- ? row
206
- : { ...row, reasoning: mapReasoning(resolved.reasoning) }
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
@@ -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 { Session, SessionEvent } from '@deepseek-ai/dsh-session'
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
- /** Structural boundary over Harness permission presets; values stay service-owned. */
13
- export interface PermissionPresetsService {
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 (ctx as unknown as { get(name: string): unknown }).get('permissionPresets') as PermissionPresetsService | undefined
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 interface PresetRow {
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
- /** Structural boundary for the optional upstream AgentPresets service. */
18
- export interface AgentPresetsService {
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 (ctx as unknown as { get(name: string): unknown }).get('agentPresets') as AgentPresetsService | undefined
16
+ return ctx.get('agentPresets')
30
17
  }
31
18
 
32
19
  /** A preset may change only before the first durable turn begins. */
@@ -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