Merge pull request 'feat: completions update, create promptrequest, LLM flag rename, ACP content fix' (#41) from feat/completions-llm-flags-promptrequest into main
This commit was merged in pull request #41.
This commit is contained in:
@@ -2,10 +2,10 @@ _mcpctl() {
|
|||||||
local cur prev words cword
|
local cur prev words cword
|
||||||
_init_completion || return
|
_init_completion || return
|
||||||
|
|
||||||
local commands="status login logout config get describe delete logs create edit apply backup restore mcp help"
|
local commands="status login logout config get describe delete logs create edit apply backup restore mcp approve help"
|
||||||
local project_commands="attach-server detach-server get describe delete logs create edit help"
|
local project_commands="attach-server detach-server get describe delete logs create edit help"
|
||||||
local global_opts="-v --version --daemon-url --direct --project -h --help"
|
local global_opts="-v --version --daemon-url --direct --project -h --help"
|
||||||
local resources="servers instances secrets templates projects users groups rbac"
|
local resources="servers instances secrets templates projects users groups rbac prompts promptrequests"
|
||||||
|
|
||||||
# Check if --project was given
|
# Check if --project was given
|
||||||
local has_project=false
|
local has_project=false
|
||||||
@@ -78,7 +78,7 @@ _mcpctl() {
|
|||||||
case "$subcmd" in
|
case "$subcmd" in
|
||||||
config)
|
config)
|
||||||
if [[ $((cword - subcmd_pos)) -eq 1 ]]; then
|
if [[ $((cword - subcmd_pos)) -eq 1 ]]; then
|
||||||
COMPREPLY=($(compgen -W "view set path reset claude impersonate help" -- "$cur"))
|
COMPREPLY=($(compgen -W "view set path reset claude claude-generate setup impersonate help" -- "$cur"))
|
||||||
fi
|
fi
|
||||||
return ;;
|
return ;;
|
||||||
status)
|
status)
|
||||||
@@ -114,7 +114,7 @@ _mcpctl() {
|
|||||||
return ;;
|
return ;;
|
||||||
create)
|
create)
|
||||||
if [[ $((cword - subcmd_pos)) -eq 1 ]]; then
|
if [[ $((cword - subcmd_pos)) -eq 1 ]]; then
|
||||||
COMPREPLY=($(compgen -W "server secret project user group rbac help" -- "$cur"))
|
COMPREPLY=($(compgen -W "server secret project user group rbac prompt promptrequest help" -- "$cur"))
|
||||||
fi
|
fi
|
||||||
return ;;
|
return ;;
|
||||||
apply)
|
apply)
|
||||||
@@ -150,6 +150,15 @@ _mcpctl() {
|
|||||||
fi
|
fi
|
||||||
COMPREPLY=($(compgen -W "$names" -- "$cur"))
|
COMPREPLY=($(compgen -W "$names" -- "$cur"))
|
||||||
return ;;
|
return ;;
|
||||||
|
approve)
|
||||||
|
if [[ -z "$resource_type" ]]; then
|
||||||
|
COMPREPLY=($(compgen -W "promptrequest" -- "$cur"))
|
||||||
|
else
|
||||||
|
local names
|
||||||
|
names=$(_mcpctl_resource_names "$resource_type")
|
||||||
|
COMPREPLY=($(compgen -W "$names" -- "$cur"))
|
||||||
|
fi
|
||||||
|
return ;;
|
||||||
help)
|
help)
|
||||||
COMPREPLY=($(compgen -W "$commands" -- "$cur"))
|
COMPREPLY=($(compgen -W "$commands" -- "$cur"))
|
||||||
return ;;
|
return ;;
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
# Erase any stale completions from previous versions
|
# Erase any stale completions from previous versions
|
||||||
complete -c mcpctl -e
|
complete -c mcpctl -e
|
||||||
|
|
||||||
set -l commands status login logout config get describe delete logs create edit apply backup restore mcp help
|
set -l commands status login logout config get describe delete logs create edit apply backup restore mcp approve help
|
||||||
set -l project_commands attach-server detach-server get describe delete logs create edit help
|
set -l project_commands attach-server detach-server get describe delete logs create edit help
|
||||||
|
|
||||||
# Disable file completions by default
|
# Disable file completions by default
|
||||||
@@ -28,7 +28,7 @@ function __mcpctl_has_project
|
|||||||
end
|
end
|
||||||
|
|
||||||
# Helper: check if a resource type has been selected after get/describe/delete/edit
|
# Helper: check if a resource type has been selected after get/describe/delete/edit
|
||||||
set -l resources servers instances secrets templates projects users groups rbac
|
set -l resources servers instances secrets templates projects users groups rbac prompts promptrequests
|
||||||
|
|
||||||
function __mcpctl_needs_resource_type
|
function __mcpctl_needs_resource_type
|
||||||
set -l tokens (commandline -opc)
|
set -l tokens (commandline -opc)
|
||||||
@@ -36,11 +36,11 @@ function __mcpctl_needs_resource_type
|
|||||||
for tok in $tokens
|
for tok in $tokens
|
||||||
if $found_cmd
|
if $found_cmd
|
||||||
# Check if next token after get/describe/delete/edit is a resource type
|
# Check if next token after get/describe/delete/edit is a resource type
|
||||||
if contains -- $tok servers instances secrets templates projects users groups rbac
|
if contains -- $tok servers instances secrets templates projects users groups rbac prompts promptrequests
|
||||||
return 1 # resource type already present
|
return 1 # resource type already present
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
if contains -- $tok get describe delete edit
|
if contains -- $tok get describe delete edit approve
|
||||||
set found_cmd true
|
set found_cmd true
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -55,12 +55,12 @@ function __mcpctl_get_resource_type
|
|||||||
set -l found_cmd false
|
set -l found_cmd false
|
||||||
for tok in $tokens
|
for tok in $tokens
|
||||||
if $found_cmd
|
if $found_cmd
|
||||||
if contains -- $tok servers instances secrets templates projects users groups rbac
|
if contains -- $tok servers instances secrets templates projects users groups rbac prompts promptrequests
|
||||||
echo $tok
|
echo $tok
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
if contains -- $tok get describe delete edit
|
if contains -- $tok get describe delete edit approve
|
||||||
set found_cmd true
|
set found_cmd true
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -139,6 +139,7 @@ complete -c mcpctl -n "not __mcpctl_has_project; and not __fish_seen_subcommand_
|
|||||||
complete -c mcpctl -n "not __mcpctl_has_project; and not __fish_seen_subcommand_from $commands" -a apply -d 'Apply configuration from file'
|
complete -c mcpctl -n "not __mcpctl_has_project; and not __fish_seen_subcommand_from $commands" -a apply -d 'Apply configuration from file'
|
||||||
complete -c mcpctl -n "not __mcpctl_has_project; and not __fish_seen_subcommand_from $commands" -a backup -d 'Backup configuration'
|
complete -c mcpctl -n "not __mcpctl_has_project; and not __fish_seen_subcommand_from $commands" -a backup -d 'Backup configuration'
|
||||||
complete -c mcpctl -n "not __mcpctl_has_project; and not __fish_seen_subcommand_from $commands" -a restore -d 'Restore from backup'
|
complete -c mcpctl -n "not __mcpctl_has_project; and not __fish_seen_subcommand_from $commands" -a restore -d 'Restore from backup'
|
||||||
|
complete -c mcpctl -n "not __mcpctl_has_project; and not __fish_seen_subcommand_from $commands" -a approve -d 'Approve a prompt request'
|
||||||
complete -c mcpctl -n "not __mcpctl_has_project; and not __fish_seen_subcommand_from $commands" -a help -d 'Show help'
|
complete -c mcpctl -n "not __mcpctl_has_project; and not __fish_seen_subcommand_from $commands" -a help -d 'Show help'
|
||||||
|
|
||||||
# Project-scoped commands (with --project)
|
# Project-scoped commands (with --project)
|
||||||
@@ -157,7 +158,7 @@ complete -c mcpctl -n "__fish_seen_subcommand_from get describe delete; and __mc
|
|||||||
complete -c mcpctl -n "__fish_seen_subcommand_from edit; and __mcpctl_needs_resource_type" -a 'servers projects' -d 'Resource type'
|
complete -c mcpctl -n "__fish_seen_subcommand_from edit; and __mcpctl_needs_resource_type" -a 'servers projects' -d 'Resource type'
|
||||||
|
|
||||||
# Resource names — after resource type is selected
|
# Resource names — after resource type is selected
|
||||||
complete -c mcpctl -n "__fish_seen_subcommand_from get describe delete edit; and not __mcpctl_needs_resource_type" -a '(__mcpctl_resource_names)' -d 'Resource name'
|
complete -c mcpctl -n "__fish_seen_subcommand_from get describe delete edit approve; and not __mcpctl_needs_resource_type" -a '(__mcpctl_resource_names)' -d 'Resource name'
|
||||||
|
|
||||||
# Helper: check if attach-server/detach-server already has a server argument
|
# Helper: check if attach-server/detach-server already has a server argument
|
||||||
function __mcpctl_needs_server_arg
|
function __mcpctl_needs_server_arg
|
||||||
@@ -196,22 +197,25 @@ complete -c mcpctl -n "__fish_seen_subcommand_from login" -l email -d 'Email add
|
|||||||
complete -c mcpctl -n "__fish_seen_subcommand_from login" -l password -d 'Password' -x
|
complete -c mcpctl -n "__fish_seen_subcommand_from login" -l password -d 'Password' -x
|
||||||
|
|
||||||
# config subcommands
|
# config subcommands
|
||||||
set -l config_cmds view set path reset claude claude-generate impersonate
|
set -l config_cmds view set path reset claude claude-generate setup impersonate
|
||||||
complete -c mcpctl -n "__fish_seen_subcommand_from config; and not __fish_seen_subcommand_from $config_cmds" -a view -d 'Show configuration'
|
complete -c mcpctl -n "__fish_seen_subcommand_from config; and not __fish_seen_subcommand_from $config_cmds" -a view -d 'Show configuration'
|
||||||
complete -c mcpctl -n "__fish_seen_subcommand_from config; and not __fish_seen_subcommand_from $config_cmds" -a set -d 'Set a config value'
|
complete -c mcpctl -n "__fish_seen_subcommand_from config; and not __fish_seen_subcommand_from $config_cmds" -a set -d 'Set a config value'
|
||||||
complete -c mcpctl -n "__fish_seen_subcommand_from config; and not __fish_seen_subcommand_from $config_cmds" -a path -d 'Show config file path'
|
complete -c mcpctl -n "__fish_seen_subcommand_from config; and not __fish_seen_subcommand_from $config_cmds" -a path -d 'Show config file path'
|
||||||
complete -c mcpctl -n "__fish_seen_subcommand_from config; and not __fish_seen_subcommand_from $config_cmds" -a reset -d 'Reset to defaults'
|
complete -c mcpctl -n "__fish_seen_subcommand_from config; and not __fish_seen_subcommand_from $config_cmds" -a reset -d 'Reset to defaults'
|
||||||
complete -c mcpctl -n "__fish_seen_subcommand_from config; and not __fish_seen_subcommand_from $config_cmds" -a claude -d 'Generate .mcp.json for project'
|
complete -c mcpctl -n "__fish_seen_subcommand_from config; and not __fish_seen_subcommand_from $config_cmds" -a claude -d 'Generate .mcp.json for project'
|
||||||
|
complete -c mcpctl -n "__fish_seen_subcommand_from config; and not __fish_seen_subcommand_from $config_cmds" -a setup -d 'Configure LLM provider'
|
||||||
complete -c mcpctl -n "__fish_seen_subcommand_from config; and not __fish_seen_subcommand_from $config_cmds" -a impersonate -d 'Impersonate a user'
|
complete -c mcpctl -n "__fish_seen_subcommand_from config; and not __fish_seen_subcommand_from $config_cmds" -a impersonate -d 'Impersonate a user'
|
||||||
|
|
||||||
# create subcommands
|
# create subcommands
|
||||||
set -l create_cmds server secret project user group rbac
|
set -l create_cmds server secret project user group rbac prompt promptrequest
|
||||||
complete -c mcpctl -n "__fish_seen_subcommand_from create; and not __fish_seen_subcommand_from $create_cmds" -a server -d 'Create a server'
|
complete -c mcpctl -n "__fish_seen_subcommand_from create; and not __fish_seen_subcommand_from $create_cmds" -a server -d 'Create a server'
|
||||||
complete -c mcpctl -n "__fish_seen_subcommand_from create; and not __fish_seen_subcommand_from $create_cmds" -a secret -d 'Create a secret'
|
complete -c mcpctl -n "__fish_seen_subcommand_from create; and not __fish_seen_subcommand_from $create_cmds" -a secret -d 'Create a secret'
|
||||||
complete -c mcpctl -n "__fish_seen_subcommand_from create; and not __fish_seen_subcommand_from $create_cmds" -a project -d 'Create a project'
|
complete -c mcpctl -n "__fish_seen_subcommand_from create; and not __fish_seen_subcommand_from $create_cmds" -a project -d 'Create a project'
|
||||||
complete -c mcpctl -n "__fish_seen_subcommand_from create; and not __fish_seen_subcommand_from $create_cmds" -a user -d 'Create a user'
|
complete -c mcpctl -n "__fish_seen_subcommand_from create; and not __fish_seen_subcommand_from $create_cmds" -a user -d 'Create a user'
|
||||||
complete -c mcpctl -n "__fish_seen_subcommand_from create; and not __fish_seen_subcommand_from $create_cmds" -a group -d 'Create a group'
|
complete -c mcpctl -n "__fish_seen_subcommand_from create; and not __fish_seen_subcommand_from $create_cmds" -a group -d 'Create a group'
|
||||||
complete -c mcpctl -n "__fish_seen_subcommand_from create; and not __fish_seen_subcommand_from $create_cmds" -a rbac -d 'Create an RBAC binding'
|
complete -c mcpctl -n "__fish_seen_subcommand_from create; and not __fish_seen_subcommand_from $create_cmds" -a rbac -d 'Create an RBAC binding'
|
||||||
|
complete -c mcpctl -n "__fish_seen_subcommand_from create; and not __fish_seen_subcommand_from $create_cmds" -a prompt -d 'Create an approved prompt'
|
||||||
|
complete -c mcpctl -n "__fish_seen_subcommand_from create; and not __fish_seen_subcommand_from $create_cmds" -a promptrequest -d 'Create a prompt request'
|
||||||
|
|
||||||
# logs options
|
# logs options
|
||||||
complete -c mcpctl -n "__fish_seen_subcommand_from logs" -l tail -d 'Number of lines' -x
|
complete -c mcpctl -n "__fish_seen_subcommand_from logs" -l tail -d 'Number of lines' -x
|
||||||
@@ -227,6 +231,9 @@ complete -c mcpctl -n "__fish_seen_subcommand_from restore" -s i -l input -d 'In
|
|||||||
complete -c mcpctl -n "__fish_seen_subcommand_from restore" -s p -l password -d 'Decryption password' -x
|
complete -c mcpctl -n "__fish_seen_subcommand_from restore" -s p -l password -d 'Decryption password' -x
|
||||||
complete -c mcpctl -n "__fish_seen_subcommand_from restore" -s c -l conflict -d 'Conflict strategy' -xa 'skip overwrite fail'
|
complete -c mcpctl -n "__fish_seen_subcommand_from restore" -s c -l conflict -d 'Conflict strategy' -xa 'skip overwrite fail'
|
||||||
|
|
||||||
|
# approve: first arg is resource type (promptrequest only), second is name
|
||||||
|
complete -c mcpctl -n "__fish_seen_subcommand_from approve; and __mcpctl_needs_resource_type" -a 'promptrequest' -d 'Resource type'
|
||||||
|
|
||||||
# apply takes a file
|
# apply takes a file
|
||||||
complete -c mcpctl -n "__fish_seen_subcommand_from apply" -s f -l file -d 'Configuration file' -rF
|
complete -c mcpctl -n "__fish_seen_subcommand_from apply" -s f -l file -d 'Configuration file' -rF
|
||||||
complete -c mcpctl -n "__fish_seen_subcommand_from apply" -F
|
complete -c mcpctl -n "__fish_seen_subcommand_from apply" -F
|
||||||
|
|||||||
@@ -196,8 +196,8 @@ export function createCreateCommand(deps: CreateCommandDeps): Command {
|
|||||||
.argument('<name>', 'Project name')
|
.argument('<name>', 'Project name')
|
||||||
.option('-d, --description <text>', 'Project description', '')
|
.option('-d, --description <text>', 'Project description', '')
|
||||||
.option('--proxy-mode <mode>', 'Proxy mode (direct, filtered)')
|
.option('--proxy-mode <mode>', 'Proxy mode (direct, filtered)')
|
||||||
.option('--proxy-mode-llm-provider <name>', 'LLM provider name (for filtered proxy mode)')
|
.option('--llm-provider <name>', 'LLM provider name')
|
||||||
.option('--proxy-mode-llm-model <name>', 'LLM model name (for filtered proxy mode)')
|
.option('--llm-model <name>', 'LLM model name')
|
||||||
.option('--prompt <text>', 'Project-level prompt / instructions for the LLM')
|
.option('--prompt <text>', 'Project-level prompt / instructions for the LLM')
|
||||||
.option('--server <name>', 'Server name (repeat for multiple)', collect, [])
|
.option('--server <name>', 'Server name (repeat for multiple)', collect, [])
|
||||||
.option('--force', 'Update if already exists')
|
.option('--force', 'Update if already exists')
|
||||||
@@ -208,8 +208,8 @@ export function createCreateCommand(deps: CreateCommandDeps): Command {
|
|||||||
proxyMode: opts.proxyMode ?? 'direct',
|
proxyMode: opts.proxyMode ?? 'direct',
|
||||||
};
|
};
|
||||||
if (opts.prompt) body.prompt = opts.prompt;
|
if (opts.prompt) body.prompt = opts.prompt;
|
||||||
if (opts.proxyModeLlmProvider) body.llmProvider = opts.proxyModeLlmProvider;
|
if (opts.llmProvider) body.llmProvider = opts.llmProvider;
|
||||||
if (opts.proxyModeLlmModel) body.llmModel = opts.proxyModeLlmModel;
|
if (opts.llmModel) body.llmModel = opts.llmModel;
|
||||||
if (opts.server.length > 0) body.servers = opts.server;
|
if (opts.server.length > 0) body.servers = opts.server;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -379,5 +379,31 @@ export function createCreateCommand(deps: CreateCommandDeps): Command {
|
|||||||
log(`prompt '${prompt.name}' created (id: ${prompt.id})`);
|
log(`prompt '${prompt.name}' created (id: ${prompt.id})`);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// --- create promptrequest ---
|
||||||
|
cmd.command('promptrequest')
|
||||||
|
.description('Create a prompt request (pending proposal that needs approval)')
|
||||||
|
.argument('<name>', 'Prompt request name (lowercase alphanumeric with hyphens)')
|
||||||
|
.requiredOption('--project <name>', 'Project name (required)')
|
||||||
|
.option('--content <text>', 'Prompt content text')
|
||||||
|
.option('--content-file <path>', 'Read prompt content from file')
|
||||||
|
.action(async (name: string, opts) => {
|
||||||
|
let content = opts.content as string | undefined;
|
||||||
|
if (opts.contentFile) {
|
||||||
|
const fs = await import('node:fs/promises');
|
||||||
|
content = await fs.readFile(opts.contentFile as string, 'utf-8');
|
||||||
|
}
|
||||||
|
if (!content) {
|
||||||
|
throw new Error('--content or --content-file is required');
|
||||||
|
}
|
||||||
|
|
||||||
|
const projectName = opts.project as string;
|
||||||
|
const pr = await client.post<{ id: string; name: string }>(
|
||||||
|
`/api/v1/projects/${encodeURIComponent(projectName)}/promptrequests`,
|
||||||
|
{ name, content },
|
||||||
|
);
|
||||||
|
log(`prompt request '${pr.name}' created (id: ${pr.id})`);
|
||||||
|
log(` approve with: mcpctl approve promptrequest ${pr.name}`);
|
||||||
|
});
|
||||||
|
|
||||||
return cmd;
|
return cmd;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,8 +30,8 @@ describe('project with new fields', () => {
|
|||||||
'project', 'smart-home',
|
'project', 'smart-home',
|
||||||
'-d', 'Smart home project',
|
'-d', 'Smart home project',
|
||||||
'--proxy-mode', 'filtered',
|
'--proxy-mode', 'filtered',
|
||||||
'--proxy-mode-llm-provider', 'gemini-cli',
|
'--llm-provider', 'gemini-cli',
|
||||||
'--proxy-mode-llm-model', 'gemini-2.0-flash',
|
'--llm-model', 'gemini-2.0-flash',
|
||||||
'--server', 'my-grafana',
|
'--server', 'my-grafana',
|
||||||
'--server', 'my-ha',
|
'--server', 'my-ha',
|
||||||
], { from: 'user' });
|
], { from: 'user' });
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ describe('fish completions', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('does not offer resource types without __mcpctl_needs_resource_type guard', () => {
|
it('does not offer resource types without __mcpctl_needs_resource_type guard', () => {
|
||||||
const resourceTypes = ['servers', 'instances', 'secrets', 'templates', 'projects', 'users', 'groups', 'rbac'];
|
const resourceTypes = ['servers', 'instances', 'secrets', 'templates', 'projects', 'users', 'groups', 'rbac', 'prompts', 'promptrequests'];
|
||||||
const lines = fishFile.split('\n').filter((l) => l.startsWith('complete '));
|
const lines = fishFile.split('\n').filter((l) => l.startsWith('complete '));
|
||||||
|
|
||||||
for (const line of lines) {
|
for (const line of lines) {
|
||||||
|
|||||||
@@ -205,16 +205,20 @@ export class AcpClient {
|
|||||||
|
|
||||||
// Collect text from agent_message_chunk
|
// Collect text from agent_message_chunk
|
||||||
if (update.sessionUpdate === 'agent_message_chunk') {
|
if (update.sessionUpdate === 'agent_message_chunk') {
|
||||||
const content = update.content as Array<{ type: string; text?: string }> | undefined;
|
const content = update.content;
|
||||||
if (content) {
|
// Gemini ACP sends content as a single object {type, text} or an array [{type, text}]
|
||||||
for (const block of content) {
|
const blocks: Array<{ type: string; text?: string }> = Array.isArray(content)
|
||||||
|
? content as Array<{ type: string; text?: string }>
|
||||||
|
: content && typeof content === 'object'
|
||||||
|
? [content as { type: string; text?: string }]
|
||||||
|
: [];
|
||||||
|
for (const block of blocks) {
|
||||||
if (block.type === 'text' && block.text) {
|
if (block.type === 'text' && block.text) {
|
||||||
this.activePromptChunks.push(block.text);
|
this.activePromptChunks.push(block.text);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/** Handle requests from the agent (e.g., session/request_permission). Reject them all. */
|
/** Handle requests from the agent (e.g., session/request_permission). Reject them all. */
|
||||||
private handleAgentRequest(msg: { id: number; method: string; params?: Record<string, unknown> }): void {
|
private handleAgentRequest(msg: { id: number; method: string; params?: Record<string, unknown> }): void {
|
||||||
|
|||||||
@@ -230,6 +230,77 @@ describe('AcpClient', () => {
|
|||||||
expect(result).toBe('Part A Part B');
|
expect(result).toBe('Part A Part B');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('handles single-object content (real Gemini ACP format)', async () => {
|
||||||
|
createClient();
|
||||||
|
autoHandshake('sess-1');
|
||||||
|
await client.ensureReady();
|
||||||
|
|
||||||
|
mock.stdin.write.mockImplementation((data: string) => {
|
||||||
|
const msg = JSON.parse(data.trim()) as { id: number; method: string };
|
||||||
|
if (msg.method === 'session/prompt') {
|
||||||
|
setImmediate(() => {
|
||||||
|
// Real Gemini ACP sends content as a single object, not an array
|
||||||
|
mock.sendLine({
|
||||||
|
jsonrpc: '2.0',
|
||||||
|
method: 'session/update',
|
||||||
|
params: {
|
||||||
|
sessionId: 'sess-1',
|
||||||
|
update: {
|
||||||
|
sessionUpdate: 'agent_message_chunk',
|
||||||
|
content: { type: 'text', text: 'ok' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
mock.sendResponse(msg.id, { stopReason: 'end_turn' });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await client.prompt('test');
|
||||||
|
expect(result).toBe('ok');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('ignores agent_thought_chunk notifications', async () => {
|
||||||
|
createClient();
|
||||||
|
autoHandshake('sess-1');
|
||||||
|
await client.ensureReady();
|
||||||
|
|
||||||
|
mock.stdin.write.mockImplementation((data: string) => {
|
||||||
|
const msg = JSON.parse(data.trim()) as { id: number; method: string };
|
||||||
|
if (msg.method === 'session/prompt') {
|
||||||
|
setImmediate(() => {
|
||||||
|
// Gemini sends thought chunks before message chunks
|
||||||
|
mock.sendLine({
|
||||||
|
jsonrpc: '2.0',
|
||||||
|
method: 'session/update',
|
||||||
|
params: {
|
||||||
|
sessionId: 'sess-1',
|
||||||
|
update: {
|
||||||
|
sessionUpdate: 'agent_thought_chunk',
|
||||||
|
content: { type: 'text', text: 'Thinking about it...' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
mock.sendLine({
|
||||||
|
jsonrpc: '2.0',
|
||||||
|
method: 'session/update',
|
||||||
|
params: {
|
||||||
|
sessionId: 'sess-1',
|
||||||
|
update: {
|
||||||
|
sessionUpdate: 'agent_message_chunk',
|
||||||
|
content: { type: 'text', text: 'ok' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
mock.sendResponse(msg.id, { stopReason: 'end_turn' });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await client.prompt('test');
|
||||||
|
expect(result).toBe('ok');
|
||||||
|
});
|
||||||
|
|
||||||
it('calls ensureReady automatically (lazy init)', async () => {
|
it('calls ensureReady automatically (lazy init)', async () => {
|
||||||
createClient();
|
createClient();
|
||||||
autoHandshake('sess-auto');
|
autoHandshake('sess-auto');
|
||||||
|
|||||||
Reference in New Issue
Block a user