mirror of
https://github.com/zsh-users/zsh-autosuggestions.git
synced 2026-02-09 16:41:32 +01:00
feat(ai): add empty buffer context suggestions
Enable AI suggestions on empty prompts with enhanced environmental context. - Update AI_MIN_INPUT default from 3 to 0 - Add ALLOW_EMPTY_BUFFER opt-in config variable - Remove empty-buffer guards in modify, suggest, enable - Add zle-line-init hook for prompt-time suggestions - Enhance history gathering with PWD-aware priority - Add env context for dir listing, git branch, status - Implement dual prompts: predict vs complete modes - Add prompt artifact stripping for $ and > prefixes - Update README with empty buffer configuration - Add tests for empty buffer and artifact stripping Empty buffer suggestions require zsh 5.3+ and work best with AI strategy, leveraging directory context, git state, and command history to predict likely next actions. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
8a9c1a2a30
commit
d89bf4ec0d
7 changed files with 282 additions and 31 deletions
19
README.md
19
README.md
|
|
@ -130,9 +130,26 @@ export ZSH_AUTOSUGGEST_STRATEGY=(ai history)
|
|||
| `ZSH_AUTOSUGGEST_AI_ENDPOINT` | `https://api.openai.com/v1/chat/completions` | API endpoint URL |
|
||||
| `ZSH_AUTOSUGGEST_AI_MODEL` | `gpt-3.5-turbo` | Model name to use |
|
||||
| `ZSH_AUTOSUGGEST_AI_TIMEOUT` | `5` | Request timeout in seconds |
|
||||
| `ZSH_AUTOSUGGEST_AI_MIN_INPUT` | `3` | Minimum input length before querying |
|
||||
| `ZSH_AUTOSUGGEST_AI_MIN_INPUT` | `0` | Minimum input length before querying |
|
||||
| `ZSH_AUTOSUGGEST_AI_HISTORY_LINES` | `20` | Number of recent history lines to send as context |
|
||||
| `ZSH_AUTOSUGGEST_AI_PREFER_PWD_HISTORY` | `yes` | Prioritize history from current directory |
|
||||
| `ZSH_AUTOSUGGEST_ALLOW_EMPTY_BUFFER` | (unset) | Set to any value to enable suggestions on empty buffer (requires zsh 5.3+) |
|
||||
|
||||
#### Empty Buffer Suggestions
|
||||
|
||||
By default, suggestions only appear when you start typing. You can enable suggestions on an empty command line by setting `ZSH_AUTOSUGGEST_ALLOW_EMPTY_BUFFER`:
|
||||
|
||||
```sh
|
||||
export ZSH_AUTOSUGGEST_ALLOW_EMPTY_BUFFER=1
|
||||
export ZSH_AUTOSUGGEST_AI_API_KEY="your-api-key-here"
|
||||
export ZSH_AUTOSUGGEST_STRATEGY=(ai history)
|
||||
```
|
||||
|
||||
**Notes:**
|
||||
- Requires zsh 5.3+ for prompt-time suggestions
|
||||
- This feature is primarily designed for the AI strategy, which can predict the next likely command based on your current directory, git status, and recent history
|
||||
- Traditional strategies (history, completion) don't benefit from empty buffer suggestions
|
||||
- **Cost consideration:** With AI strategy, this will make an API request on every new prompt, which may increase API costs
|
||||
|
||||
#### Examples
|
||||
|
||||
|
|
|
|||
|
|
@ -171,3 +171,73 @@ EOF
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'prompt artifact stripping' do
|
||||
let(:options) { ["ZSH_AUTOSUGGEST_AI_API_KEY=test-key", "ZSH_AUTOSUGGEST_STRATEGY=(ai)"] }
|
||||
|
||||
let(:before_sourcing) do
|
||||
-> {
|
||||
session.run_command('curl() {
|
||||
if [[ "$*" == *"max-time"* ]]; then
|
||||
cat <<EOFCURL
|
||||
{"choices":[{"message":{"content":"$ git status"}}]}
|
||||
200
|
||||
EOFCURL
|
||||
fi
|
||||
}')
|
||||
}
|
||||
end
|
||||
|
||||
it 'strips $ prompt artifact' do
|
||||
session.send_string('git st')
|
||||
wait_for { session.content }.to eq('git status')
|
||||
end
|
||||
end
|
||||
|
||||
context 'empty buffer suggestions' do
|
||||
let(:options) { ["ZSH_AUTOSUGGEST_AI_API_KEY=test-key", "ZSH_AUTOSUGGEST_ALLOW_EMPTY_BUFFER=1", "ZSH_AUTOSUGGEST_STRATEGY=(ai)"] }
|
||||
|
||||
let(:before_sourcing) do
|
||||
-> {
|
||||
session.run_command('curl() {
|
||||
if [[ "$*" == *"max-time"* ]]; then
|
||||
cat <<EOFCURL
|
||||
{"choices":[{"message":{"content":"git status"}}]}
|
||||
200
|
||||
EOFCURL
|
||||
fi
|
||||
}')
|
||||
}
|
||||
end
|
||||
|
||||
it 'suggests command on empty buffer when enabled' do
|
||||
session.send_keys('C-c')
|
||||
wait_for { session.content(esc_seqs: true) }.to match(/git status/)
|
||||
end
|
||||
end
|
||||
|
||||
context 'empty buffer without flag' do
|
||||
let(:options) { ["ZSH_AUTOSUGGEST_AI_API_KEY=test-key", "ZSH_AUTOSUGGEST_STRATEGY=(ai history)"] }
|
||||
|
||||
let(:before_sourcing) do
|
||||
-> {
|
||||
session.run_command('curl() {
|
||||
if [[ "$*" == *"max-time"* ]]; then
|
||||
cat <<EOFCURL
|
||||
{"choices":[{"message":{"content":"git status"}}]}
|
||||
200
|
||||
EOFCURL
|
||||
fi
|
||||
}')
|
||||
}
|
||||
end
|
||||
|
||||
it 'does not suggest on empty buffer by default' do
|
||||
with_history('git status') do
|
||||
sleep 0.5
|
||||
expect(session.content).to_not match(/git status/)
|
||||
session.send_string('git')
|
||||
wait_for { session.content }.to eq('git status')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -109,7 +109,7 @@ typeset -g ZSH_AUTOSUGGEST_AI_TIMEOUT=5
|
|||
|
||||
# Minimum input length before querying AI
|
||||
(( ! ${+ZSH_AUTOSUGGEST_AI_MIN_INPUT} )) &&
|
||||
typeset -g ZSH_AUTOSUGGEST_AI_MIN_INPUT=3
|
||||
typeset -g ZSH_AUTOSUGGEST_AI_MIN_INPUT=0
|
||||
|
||||
# Number of recent history lines to include as context
|
||||
(( ! ${+ZSH_AUTOSUGGEST_AI_HISTORY_LINES} )) &&
|
||||
|
|
@ -118,3 +118,7 @@ typeset -g ZSH_AUTOSUGGEST_AI_HISTORY_LINES=20
|
|||
# Prefer history entries from current directory
|
||||
(( ! ${+ZSH_AUTOSUGGEST_AI_PREFER_PWD_HISTORY} )) &&
|
||||
typeset -g ZSH_AUTOSUGGEST_AI_PREFER_PWD_HISTORY=yes
|
||||
|
||||
# Allow suggestions on empty buffer (opt-in, for AI strategy)
|
||||
# Set to any value to enable. Unset by default.
|
||||
# Uses (( ${+VAR} )) pattern like ZSH_AUTOSUGGEST_MANUAL_REBIND
|
||||
|
|
|
|||
|
|
@ -31,3 +31,17 @@ fi
|
|||
|
||||
# Start the autosuggestion widgets on the next precmd
|
||||
add-zsh-hook precmd _zsh_autosuggest_start
|
||||
|
||||
_zsh_autosuggest_line_init() {
|
||||
emulate -L zsh
|
||||
if (( ${+ZSH_AUTOSUGGEST_ALLOW_EMPTY_BUFFER} )) && \
|
||||
(( ! ${+_ZSH_AUTOSUGGEST_DISABLED} )); then
|
||||
_zsh_autosuggest_fetch
|
||||
fi
|
||||
}
|
||||
|
||||
# Use add-zle-hook-widget (zsh 5.3+) to avoid conflicts with other plugins
|
||||
if (( ${+functions[add-zle-hook-widget]} )) || \
|
||||
autoload -Uz add-zle-hook-widget 2>/dev/null; then
|
||||
add-zle-hook-widget zle-line-init _zsh_autosuggest_line_init
|
||||
fi
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
|
||||
#--------------------------------------------------------------------#
|
||||
# AI Suggestion Strategy #
|
||||
# AI Suggestion Strategy #
|
||||
#--------------------------------------------------------------------#
|
||||
# Queries an OpenAI-compatible LLM API to generate command
|
||||
# completions based on partial input, working directory, and
|
||||
|
|
@ -50,22 +50,54 @@ _zsh_autosuggest_strategy_ai_gather_context() {
|
|||
line="${line:0:200}..."
|
||||
fi
|
||||
|
||||
# Categorize by PWD relevance
|
||||
if [[ "$prefer_pwd" == "yes" ]] && [[ "$line" == *"$pwd_basename"* || "$line" == *"./"* || "$line" == *"../"* ]]; then
|
||||
# Categorize by PWD relevance - match full path, basename, or PWD-relevant commands
|
||||
if [[ "$prefer_pwd" == "yes" ]] && [[ "$line" == *"$PWD"* || "$line" == *"$pwd_basename"* || "$line" == cd* || "$line" == ls* || "$line" == "git "* || "$line" == *"./"* || "$line" == *"../"* ]]; then
|
||||
pwd_lines+=("$line")
|
||||
else
|
||||
other_lines+=("$line")
|
||||
fi
|
||||
done
|
||||
|
||||
# Cap PWD lines at 2/3 of max to maintain diversity
|
||||
local pwd_max=$(( (max_lines * 2) / 3 ))
|
||||
local pwd_count=${#pwd_lines}
|
||||
[[ $pwd_count -gt $pwd_max ]] && pwd_count=$pwd_max
|
||||
|
||||
# Prioritize PWD-relevant lines, then fill with others
|
||||
context_lines=("${pwd_lines[@]}" "${other_lines[@]}")
|
||||
context_lines=("${(@)pwd_lines[1,$pwd_count]}" "${other_lines[@]}")
|
||||
context_lines=("${(@)context_lines[1,$max_lines]}")
|
||||
|
||||
# Return via reply array
|
||||
reply=("${context_lines[@]}")
|
||||
}
|
||||
|
||||
_zsh_autosuggest_strategy_ai_gather_env_context() {
|
||||
# Reset options to defaults and enable LOCAL_OPTIONS
|
||||
emulate -L zsh
|
||||
|
||||
local -A env_info
|
||||
|
||||
# Directory listing (up to 20 entries)
|
||||
local dir_contents
|
||||
dir_contents=$(command ls -1 2>/dev/null | head -20 | tr '\n' ', ' | sed 's/, $//')
|
||||
[[ -n "$dir_contents" ]] && env_info[dir_contents]="$dir_contents"
|
||||
|
||||
# Git branch (try two methods)
|
||||
local git_branch
|
||||
git_branch=$(command git branch --show-current 2>/dev/null)
|
||||
[[ -z "$git_branch" ]] && git_branch=$(command git rev-parse --abbrev-ref HEAD 2>/dev/null)
|
||||
[[ -n "$git_branch" ]] && env_info[git_branch]="$git_branch"
|
||||
|
||||
# Git status (up to 10 lines)
|
||||
local git_status
|
||||
git_status=$(command git status --porcelain 2>/dev/null | head -10 | tr '\n' '; ' | sed 's/; $//')
|
||||
[[ -n "$git_status" ]] && env_info[git_status]="$git_status"
|
||||
|
||||
# Return via reply associative array
|
||||
typeset -gA reply
|
||||
reply=("${(@kv)env_info}")
|
||||
}
|
||||
|
||||
_zsh_autosuggest_strategy_ai_normalize() {
|
||||
# Reset options to defaults and enable LOCAL_OPTIONS
|
||||
emulate -L zsh
|
||||
|
|
@ -77,6 +109,10 @@ _zsh_autosuggest_strategy_ai_normalize() {
|
|||
# Strip \r
|
||||
response="${response//$'\r'/}"
|
||||
|
||||
# Strip leading prompt artifacts ($ or >)
|
||||
response="${response##\$ }"
|
||||
response="${response##> }"
|
||||
|
||||
# Strip markdown code fences
|
||||
response="${response##\`\`\`*$'\n'}"
|
||||
response="${response%%$'\n'\`\`\`}"
|
||||
|
|
@ -119,14 +155,19 @@ _zsh_autosuggest_strategy_ai() {
|
|||
[[ -z "${commands[curl]}" ]] || [[ -z "${commands[jq]}" ]] && return
|
||||
|
||||
# Early return if input too short
|
||||
local min_input="${ZSH_AUTOSUGGEST_AI_MIN_INPUT:-3}"
|
||||
local min_input="${ZSH_AUTOSUGGEST_AI_MIN_INPUT:-0}"
|
||||
[[ ${#buffer} -lt $min_input ]] && return
|
||||
|
||||
# Gather context
|
||||
# Gather history context
|
||||
local -a context
|
||||
_zsh_autosuggest_strategy_ai_gather_context
|
||||
context=("${reply[@]}")
|
||||
|
||||
# Gather environment context
|
||||
local -A env_context
|
||||
_zsh_autosuggest_strategy_ai_gather_env_context
|
||||
env_context=("${(@kv)reply}")
|
||||
|
||||
# Build context string
|
||||
local context_str=""
|
||||
for line in "${context[@]}"; do
|
||||
|
|
@ -136,23 +177,44 @@ _zsh_autosuggest_strategy_ai() {
|
|||
context_str+="\"$(_zsh_autosuggest_strategy_ai_json_escape "$line")\""
|
||||
done
|
||||
|
||||
# Build JSON request body
|
||||
local system_prompt="You are a shell command auto-completion engine. Given the user's partial command, working directory, and recent history, predict the complete command. Reply ONLY with the complete command. No explanations, no markdown, no quotes."
|
||||
local user_message="Working directory: $PWD\nRecent history: [$context_str]\nPartial command: $buffer"
|
||||
# Determine prompt mode (empty vs non-empty buffer)
|
||||
local system_prompt user_message temperature
|
||||
if [[ -z "$buffer" ]]; then
|
||||
# Empty buffer: predict next command
|
||||
system_prompt="You are a shell command prediction engine. Based on the working directory, directory contents, git status, and recent history, suggest the single most likely next command the user wants to run. Reply ONLY with the complete command. No explanations, no markdown, no quotes."
|
||||
temperature="0.5"
|
||||
user_message="Working directory: $PWD"
|
||||
[[ -n "${env_context[dir_contents]}" ]] && user_message+="\nDirectory contents: ${env_context[dir_contents]}"
|
||||
[[ -n "${env_context[git_branch]}" ]] && user_message+="\nGit branch: ${env_context[git_branch]}"
|
||||
[[ -n "${env_context[git_status]}" ]] && user_message+="\nGit changes: ${env_context[git_status]}"
|
||||
user_message+="\nRecent history: [$context_str]"
|
||||
else
|
||||
# Non-empty buffer: complete partial command
|
||||
system_prompt="You are a shell command auto-completion engine. Given the user's partial command, working directory, and recent history, predict the complete command. Reply ONLY with the complete command. No explanations, no markdown, no quotes."
|
||||
temperature="0.3"
|
||||
user_message="Working directory: $PWD"
|
||||
[[ -n "${env_context[dir_contents]}" ]] && user_message+="\nDirectory contents: ${env_context[dir_contents]}"
|
||||
[[ -n "${env_context[git_branch]}" ]] && user_message+="\nGit branch: ${env_context[git_branch]}"
|
||||
[[ -n "${env_context[git_status]}" ]] && user_message+="\nGit changes: ${env_context[git_status]}"
|
||||
user_message+="\nRecent history: [$context_str]"
|
||||
user_message+="\nPartial command: $buffer"
|
||||
fi
|
||||
|
||||
# Build JSON request body
|
||||
local json_body
|
||||
json_body=$(printf '{
|
||||
"model": "%s",
|
||||
"messages": [
|
||||
{"role": "system", "content": "%s"},
|
||||
{"role": "system", "content": "%s"},
|
||||
{"role": "user", "content": "%s"}
|
||||
],
|
||||
"temperature": 0.3,
|
||||
"temperature": %s,
|
||||
"max_tokens": 100
|
||||
}' \
|
||||
"${ZSH_AUTOSUGGEST_AI_MODEL:-gpt-3.5-turbo}" \
|
||||
"$(_zsh_autosuggest_strategy_ai_json_escape "$system_prompt")" \
|
||||
"$(_zsh_autosuggest_strategy_ai_json_escape "$user_message")")
|
||||
"$(_zsh_autosuggest_strategy_ai_json_escape "$user_message")" \
|
||||
"$temperature")
|
||||
|
||||
# Make API request
|
||||
local endpoint="${ZSH_AUTOSUGGEST_AI_ENDPOINT:-https://api.openai.com/v1/chat/completions}"
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ _zsh_autosuggest_disable() {
|
|||
_zsh_autosuggest_enable() {
|
||||
unset _ZSH_AUTOSUGGEST_DISABLED
|
||||
|
||||
if (( $#BUFFER )); then
|
||||
if (( $#BUFFER )) || (( ${+ZSH_AUTOSUGGEST_ALLOW_EMPTY_BUFFER} )); then
|
||||
_zsh_autosuggest_fetch
|
||||
fi
|
||||
}
|
||||
|
|
@ -77,6 +77,8 @@ _zsh_autosuggest_modify() {
|
|||
if [[ -z "$ZSH_AUTOSUGGEST_BUFFER_MAX_SIZE" ]] || (( $#BUFFER <= $ZSH_AUTOSUGGEST_BUFFER_MAX_SIZE )); then
|
||||
_zsh_autosuggest_fetch
|
||||
fi
|
||||
elif (( ${+ZSH_AUTOSUGGEST_ALLOW_EMPTY_BUFFER} )); then
|
||||
_zsh_autosuggest_fetch
|
||||
fi
|
||||
|
||||
return $retval
|
||||
|
|
@ -99,7 +101,7 @@ _zsh_autosuggest_suggest() {
|
|||
|
||||
local suggestion="$1"
|
||||
|
||||
if [[ -n "$suggestion" ]] && (( $#BUFFER )); then
|
||||
if [[ -n "$suggestion" ]] && { (( $#BUFFER )) || (( ${+ZSH_AUTOSUGGEST_ALLOW_EMPTY_BUFFER} )); }; then
|
||||
POSTDISPLAY="${suggestion#$BUFFER}"
|
||||
else
|
||||
POSTDISPLAY=
|
||||
|
|
|
|||
|
|
@ -135,7 +135,7 @@ typeset -g ZSH_AUTOSUGGEST_AI_TIMEOUT=5
|
|||
|
||||
# Minimum input length before querying AI
|
||||
(( ! ${+ZSH_AUTOSUGGEST_AI_MIN_INPUT} )) &&
|
||||
typeset -g ZSH_AUTOSUGGEST_AI_MIN_INPUT=3
|
||||
typeset -g ZSH_AUTOSUGGEST_AI_MIN_INPUT=0
|
||||
|
||||
# Number of recent history lines to include as context
|
||||
(( ! ${+ZSH_AUTOSUGGEST_AI_HISTORY_LINES} )) &&
|
||||
|
|
@ -145,6 +145,10 @@ typeset -g ZSH_AUTOSUGGEST_AI_HISTORY_LINES=20
|
|||
(( ! ${+ZSH_AUTOSUGGEST_AI_PREFER_PWD_HISTORY} )) &&
|
||||
typeset -g ZSH_AUTOSUGGEST_AI_PREFER_PWD_HISTORY=yes
|
||||
|
||||
# Allow suggestions on empty buffer (opt-in, for AI strategy)
|
||||
# Set to any value to enable. Unset by default.
|
||||
# Uses (( ${+VAR} )) pattern like ZSH_AUTOSUGGEST_MANUAL_REBIND
|
||||
|
||||
#--------------------------------------------------------------------#
|
||||
# Utility Functions #
|
||||
#--------------------------------------------------------------------#
|
||||
|
|
@ -302,7 +306,7 @@ _zsh_autosuggest_disable() {
|
|||
_zsh_autosuggest_enable() {
|
||||
unset _ZSH_AUTOSUGGEST_DISABLED
|
||||
|
||||
if (( $#BUFFER )); then
|
||||
if (( $#BUFFER )) || (( ${+ZSH_AUTOSUGGEST_ALLOW_EMPTY_BUFFER} )); then
|
||||
_zsh_autosuggest_fetch
|
||||
fi
|
||||
}
|
||||
|
|
@ -366,6 +370,8 @@ _zsh_autosuggest_modify() {
|
|||
if [[ -z "$ZSH_AUTOSUGGEST_BUFFER_MAX_SIZE" ]] || (( $#BUFFER <= $ZSH_AUTOSUGGEST_BUFFER_MAX_SIZE )); then
|
||||
_zsh_autosuggest_fetch
|
||||
fi
|
||||
elif (( ${+ZSH_AUTOSUGGEST_ALLOW_EMPTY_BUFFER} )); then
|
||||
_zsh_autosuggest_fetch
|
||||
fi
|
||||
|
||||
return $retval
|
||||
|
|
@ -388,7 +394,7 @@ _zsh_autosuggest_suggest() {
|
|||
|
||||
local suggestion="$1"
|
||||
|
||||
if [[ -n "$suggestion" ]] && (( $#BUFFER )); then
|
||||
if [[ -n "$suggestion" ]] && { (( $#BUFFER )) || (( ${+ZSH_AUTOSUGGEST_ALLOW_EMPTY_BUFFER} )); }; then
|
||||
POSTDISPLAY="${suggestion#$BUFFER}"
|
||||
else
|
||||
POSTDISPLAY=
|
||||
|
|
@ -520,7 +526,7 @@ _zsh_autosuggest_partial_accept() {
|
|||
}
|
||||
|
||||
#--------------------------------------------------------------------#
|
||||
# AI Suggestion Strategy #
|
||||
# AI Suggestion Strategy #
|
||||
#--------------------------------------------------------------------#
|
||||
# Queries an OpenAI-compatible LLM API to generate command
|
||||
# completions based on partial input, working directory, and
|
||||
|
|
@ -570,22 +576,54 @@ _zsh_autosuggest_strategy_ai_gather_context() {
|
|||
line="${line:0:200}..."
|
||||
fi
|
||||
|
||||
# Categorize by PWD relevance
|
||||
if [[ "$prefer_pwd" == "yes" ]] && [[ "$line" == *"$pwd_basename"* || "$line" == *"./"* || "$line" == *"../"* ]]; then
|
||||
# Categorize by PWD relevance - match full path, basename, or PWD-relevant commands
|
||||
if [[ "$prefer_pwd" == "yes" ]] && [[ "$line" == *"$PWD"* || "$line" == *"$pwd_basename"* || "$line" == cd* || "$line" == ls* || "$line" == "git "* || "$line" == *"./"* || "$line" == *"../"* ]]; then
|
||||
pwd_lines+=("$line")
|
||||
else
|
||||
other_lines+=("$line")
|
||||
fi
|
||||
done
|
||||
|
||||
# Cap PWD lines at 2/3 of max to maintain diversity
|
||||
local pwd_max=$(( (max_lines * 2) / 3 ))
|
||||
local pwd_count=${#pwd_lines}
|
||||
[[ $pwd_count -gt $pwd_max ]] && pwd_count=$pwd_max
|
||||
|
||||
# Prioritize PWD-relevant lines, then fill with others
|
||||
context_lines=("${pwd_lines[@]}" "${other_lines[@]}")
|
||||
context_lines=("${(@)pwd_lines[1,$pwd_count]}" "${other_lines[@]}")
|
||||
context_lines=("${(@)context_lines[1,$max_lines]}")
|
||||
|
||||
# Return via reply array
|
||||
reply=("${context_lines[@]}")
|
||||
}
|
||||
|
||||
_zsh_autosuggest_strategy_ai_gather_env_context() {
|
||||
# Reset options to defaults and enable LOCAL_OPTIONS
|
||||
emulate -L zsh
|
||||
|
||||
local -A env_info
|
||||
|
||||
# Directory listing (up to 20 entries)
|
||||
local dir_contents
|
||||
dir_contents=$(command ls -1 2>/dev/null | head -20 | tr '\n' ', ' | sed 's/, $//')
|
||||
[[ -n "$dir_contents" ]] && env_info[dir_contents]="$dir_contents"
|
||||
|
||||
# Git branch (try two methods)
|
||||
local git_branch
|
||||
git_branch=$(command git branch --show-current 2>/dev/null)
|
||||
[[ -z "$git_branch" ]] && git_branch=$(command git rev-parse --abbrev-ref HEAD 2>/dev/null)
|
||||
[[ -n "$git_branch" ]] && env_info[git_branch]="$git_branch"
|
||||
|
||||
# Git status (up to 10 lines)
|
||||
local git_status
|
||||
git_status=$(command git status --porcelain 2>/dev/null | head -10 | tr '\n' '; ' | sed 's/; $//')
|
||||
[[ -n "$git_status" ]] && env_info[git_status]="$git_status"
|
||||
|
||||
# Return via reply associative array
|
||||
typeset -gA reply
|
||||
reply=("${(@kv)env_info}")
|
||||
}
|
||||
|
||||
_zsh_autosuggest_strategy_ai_normalize() {
|
||||
# Reset options to defaults and enable LOCAL_OPTIONS
|
||||
emulate -L zsh
|
||||
|
|
@ -597,6 +635,10 @@ _zsh_autosuggest_strategy_ai_normalize() {
|
|||
# Strip \r
|
||||
response="${response//$'\r'/}"
|
||||
|
||||
# Strip leading prompt artifacts ($ or >)
|
||||
response="${response##\$ }"
|
||||
response="${response##> }"
|
||||
|
||||
# Strip markdown code fences
|
||||
response="${response##\`\`\`*$'\n'}"
|
||||
response="${response%%$'\n'\`\`\`}"
|
||||
|
|
@ -639,14 +681,19 @@ _zsh_autosuggest_strategy_ai() {
|
|||
[[ -z "${commands[curl]}" ]] || [[ -z "${commands[jq]}" ]] && return
|
||||
|
||||
# Early return if input too short
|
||||
local min_input="${ZSH_AUTOSUGGEST_AI_MIN_INPUT:-3}"
|
||||
local min_input="${ZSH_AUTOSUGGEST_AI_MIN_INPUT:-0}"
|
||||
[[ ${#buffer} -lt $min_input ]] && return
|
||||
|
||||
# Gather context
|
||||
# Gather history context
|
||||
local -a context
|
||||
_zsh_autosuggest_strategy_ai_gather_context
|
||||
context=("${reply[@]}")
|
||||
|
||||
# Gather environment context
|
||||
local -A env_context
|
||||
_zsh_autosuggest_strategy_ai_gather_env_context
|
||||
env_context=("${(@kv)reply}")
|
||||
|
||||
# Build context string
|
||||
local context_str=""
|
||||
for line in "${context[@]}"; do
|
||||
|
|
@ -656,23 +703,44 @@ _zsh_autosuggest_strategy_ai() {
|
|||
context_str+="\"$(_zsh_autosuggest_strategy_ai_json_escape "$line")\""
|
||||
done
|
||||
|
||||
# Build JSON request body
|
||||
local system_prompt="You are a shell command auto-completion engine. Given the user's partial command, working directory, and recent history, predict the complete command. Reply ONLY with the complete command. No explanations, no markdown, no quotes."
|
||||
local user_message="Working directory: $PWD\nRecent history: [$context_str]\nPartial command: $buffer"
|
||||
# Determine prompt mode (empty vs non-empty buffer)
|
||||
local system_prompt user_message temperature
|
||||
if [[ -z "$buffer" ]]; then
|
||||
# Empty buffer: predict next command
|
||||
system_prompt="You are a shell command prediction engine. Based on the working directory, directory contents, git status, and recent history, suggest the single most likely next command the user wants to run. Reply ONLY with the complete command. No explanations, no markdown, no quotes."
|
||||
temperature="0.5"
|
||||
user_message="Working directory: $PWD"
|
||||
[[ -n "${env_context[dir_contents]}" ]] && user_message+="\nDirectory contents: ${env_context[dir_contents]}"
|
||||
[[ -n "${env_context[git_branch]}" ]] && user_message+="\nGit branch: ${env_context[git_branch]}"
|
||||
[[ -n "${env_context[git_status]}" ]] && user_message+="\nGit changes: ${env_context[git_status]}"
|
||||
user_message+="\nRecent history: [$context_str]"
|
||||
else
|
||||
# Non-empty buffer: complete partial command
|
||||
system_prompt="You are a shell command auto-completion engine. Given the user's partial command, working directory, and recent history, predict the complete command. Reply ONLY with the complete command. No explanations, no markdown, no quotes."
|
||||
temperature="0.3"
|
||||
user_message="Working directory: $PWD"
|
||||
[[ -n "${env_context[dir_contents]}" ]] && user_message+="\nDirectory contents: ${env_context[dir_contents]}"
|
||||
[[ -n "${env_context[git_branch]}" ]] && user_message+="\nGit branch: ${env_context[git_branch]}"
|
||||
[[ -n "${env_context[git_status]}" ]] && user_message+="\nGit changes: ${env_context[git_status]}"
|
||||
user_message+="\nRecent history: [$context_str]"
|
||||
user_message+="\nPartial command: $buffer"
|
||||
fi
|
||||
|
||||
# Build JSON request body
|
||||
local json_body
|
||||
json_body=$(printf '{
|
||||
"model": "%s",
|
||||
"messages": [
|
||||
{"role": "system", "content": "%s"},
|
||||
{"role": "system", "content": "%s"},
|
||||
{"role": "user", "content": "%s"}
|
||||
],
|
||||
"temperature": 0.3,
|
||||
"temperature": %s,
|
||||
"max_tokens": 100
|
||||
}' \
|
||||
"${ZSH_AUTOSUGGEST_AI_MODEL:-gpt-3.5-turbo}" \
|
||||
"$(_zsh_autosuggest_strategy_ai_json_escape "$system_prompt")" \
|
||||
"$(_zsh_autosuggest_strategy_ai_json_escape "$user_message")")
|
||||
"$(_zsh_autosuggest_strategy_ai_json_escape "$user_message")" \
|
||||
"$temperature")
|
||||
|
||||
# Make API request
|
||||
local endpoint="${ZSH_AUTOSUGGEST_AI_ENDPOINT:-https://api.openai.com/v1/chat/completions}"
|
||||
|
|
@ -1082,3 +1150,17 @@ fi
|
|||
|
||||
# Start the autosuggestion widgets on the next precmd
|
||||
add-zsh-hook precmd _zsh_autosuggest_start
|
||||
|
||||
_zsh_autosuggest_line_init() {
|
||||
emulate -L zsh
|
||||
if (( ${+ZSH_AUTOSUGGEST_ALLOW_EMPTY_BUFFER} )) && \
|
||||
(( ! ${+_ZSH_AUTOSUGGEST_DISABLED} )); then
|
||||
_zsh_autosuggest_fetch
|
||||
fi
|
||||
}
|
||||
|
||||
# Use add-zle-hook-widget (zsh 5.3+) to avoid conflicts with other plugins
|
||||
if (( ${+functions[add-zle-hook-widget]} )) || \
|
||||
autoload -Uz add-zle-hook-widget 2>/dev/null; then
|
||||
add-zle-hook-widget zle-line-init _zsh_autosuggest_line_init
|
||||
fi
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue