{"meta":{"title":"用户提示提交挂钩","intro":"用户提交消息时将调用onUserPromptSubmitted钩子。 使用它可执行以下操作：","product":"GitHub Copilot","breadcrumbs":[{"href":"/zh/copilot","title":"GitHub Copilot"},{"href":"/zh/copilot/how-tos","title":"操作方法"},{"href":"/zh/copilot/how-tos/copilot-sdk","title":"Copilot SDK"},{"href":"/zh/copilot/how-tos/copilot-sdk/hooks","title":"使用挂钩"},{"href":"/zh/copilot/how-tos/copilot-sdk/hooks/user-prompt-submitted","title":"用户提示已提交"}],"documentType":"article"},"body":"# 用户提示提交挂钩\n\n用户提交消息时将调用onUserPromptSubmitted钩子。 使用它可执行以下操作：\n\n<!-- markdownlint-disable GHD046 GHD005 -->\n\n<!-- Suppressed: GHD046 (outdated release terminology), GHD005 (hardcoded data variable) -->\n\n* 修改或增强用户提示\n* 在处理之前添加上下文\n* 筛选或验证用户输入\n* 实现提示模板\n\n## 挂钩签名\n\n<div class=\"ghd-codetabs\">\n<div class=\"ghd-codetab\" data-lang=\"typescript\" data-label=\"TypeScript\"><div class=\"ghd-codetab-fallback-label\" role=\"heading\" aria-level=\"3\">TypeScript</div>\n\n```typescript\ntype UserPromptSubmittedHandler = (\n  input: UserPromptSubmittedHookInput,\n  invocation: HookInvocation\n) => Promise<UserPromptSubmittedHookOutput | null | undefined>;\n```\n\n</div>\n\n<div class=\"ghd-codetab\" data-lang=\"python\" data-label=\"Python\"><div class=\"ghd-codetab-fallback-label\" role=\"heading\" aria-level=\"3\">Python</div>\n\n```python\nUserPromptSubmittedHandler = Callable[\n    [UserPromptSubmittedHookInput, dict[str, str]],\n    Awaitable[UserPromptSubmittedHookOutput | None]\n]\n```\n\n</div>\n\n<div class=\"ghd-codetab\" data-lang=\"go\" data-label=\"Go\"><div class=\"ghd-codetab-fallback-label\" role=\"heading\" aria-level=\"3\">Go</div>\n\n```golang\ntype UserPromptSubmittedHandler func(\n    input UserPromptSubmittedHookInput,\n    invocation HookInvocation,\n) (*UserPromptSubmittedHookOutput, error)\n```\n\n</div>\n\n<div class=\"ghd-codetab\" data-lang=\"dotnet\" data-label=\".NET\"><div class=\"ghd-codetab-fallback-label\" role=\"heading\" aria-level=\"3\">.NET</div>\n\n```csharp\npublic delegate Task<UserPromptSubmittedHookOutput?> UserPromptSubmittedHandler(\n    UserPromptSubmittedHookInput input,\n    HookInvocation invocation);\n```\n\n</div>\n\n<div class=\"ghd-codetab\" data-lang=\"java\" data-label=\"Java\"><div class=\"ghd-codetab-fallback-label\" role=\"heading\" aria-level=\"3\">Java</div>\n\n```java\n@FunctionalInterface\npublic interface UserPromptSubmittedHandler {\n    CompletableFuture<UserPromptSubmittedHookOutput> handle(\n        UserPromptSubmittedHookInput input,\n        HookInvocation invocation);\n}\n```\n\n</div>\n\n</div>\n\n## 输入\n\n| 领域          | 类型     | 说明              |\n| ----------- | ------ | --------------- |\n| `timestamp` | number | 触发挂钩时的 Unix 时间戳 |\n| `cwd`       | 字符串    | 当前工作目录          |\n| `prompt`    | 字符串    | 用户提交的提示信息       |\n\n## 输出\n\n返回 `null` 或 `undefined` 以保持提示不变。 否则，返回包含以下任何字段的对象：\n\n| 领域                  | 类型      | 说明                    |\n| ------------------- | ------- | --------------------- |\n| `modifiedPrompt`    | 字符串     | 使用修改后的提示代替原始提示        |\n| `additionalContext` | 字符串     | 向对话添加额外上下文            |\n| `suppressOutput`    | boolean | 如果为 true，则禁止显示助手的响应输出 |\n\n## 例子\n\n### 记录所有用户提示\n\n<div class=\"ghd-codetabs\">\n<div class=\"ghd-codetab\" data-lang=\"typescript\" data-label=\"TypeScript\"><div class=\"ghd-codetab-fallback-label\" role=\"heading\" aria-level=\"3\">TypeScript</div>\n\n```typescript\nconst session = await client.createSession({\n  hooks: {\n    onUserPromptSubmitted: async (input, invocation) => {\n      console.log(`[${invocation.sessionId}] User: ${input.prompt}`);\n      return null; // Pass through unchanged\n    },\n  },\n});\n```\n\n</div>\n\n<div class=\"ghd-codetab\" data-lang=\"python\" data-label=\"Python\"><div class=\"ghd-codetab-fallback-label\" role=\"heading\" aria-level=\"3\">Python</div>\n\n```python\nfrom copilot.session import PermissionHandler\n\nasync def on_user_prompt_submitted(input_data, invocation):\n    print(f\"[{invocation['session_id']}] User: {input_data['prompt']}\")\n    return None\n\nsession = await client.create_session(on_permission_request=PermissionHandler.approve_all, hooks={\"on_user_prompt_submitted\": on_user_prompt_submitted})\n```\n\n</div>\n\n<div class=\"ghd-codetab\" data-lang=\"go\" data-label=\"Go\"><div class=\"ghd-codetab-fallback-label\" role=\"heading\" aria-level=\"3\">Go</div>\n\n```golang\nsession, _ := client.CreateSession(context.Background(), &copilot.SessionConfig{\n    Hooks: &copilot.SessionHooks{\n        OnUserPromptSubmitted: func(input copilot.UserPromptSubmittedHookInput, inv copilot.HookInvocation) (*copilot.UserPromptSubmittedHookOutput, error) {\n            fmt.Printf(\"[%s] User: %s\\n\", inv.SessionID, input.Prompt)\n            return nil, nil\n        },\n    },\n})\n```\n\n</div>\n\n<div class=\"ghd-codetab\" data-lang=\"dotnet\" data-label=\".NET\"><div class=\"ghd-codetab-fallback-label\" role=\"heading\" aria-level=\"3\">.NET</div>\n\n```csharp\nvar session = await client.CreateSessionAsync(new SessionConfig\n{\n    Hooks = new SessionHooks\n    {\n        OnUserPromptSubmitted = (input, invocation) =>\n        {\n            Console.WriteLine($\"[{invocation.SessionId}] User: {input.Prompt}\");\n            return Task.FromResult<UserPromptSubmittedHookOutput?>(null);\n        },\n    },\n});\n```\n\n</div>\n\n<div class=\"ghd-codetab\" data-lang=\"java\" data-label=\"Java\"><div class=\"ghd-codetab-fallback-label\" role=\"heading\" aria-level=\"3\">Java</div>\n\n<!-- docs-validate: skip -->\n\n```java\nimport com.github.copilot.*;\nimport com.github.copilot.rpc.*;\nimport java.util.concurrent.CompletableFuture;\n\nvar hooks = new SessionHooks()\n    .setOnUserPromptSubmitted((input, invocation) -> {\n        System.out.println(\"[\" + invocation.getSessionId() + \"] User: \" + input.prompt());\n        return CompletableFuture.completedFuture(null);\n    });\n\nvar session = client.createSession(\n    new SessionConfig()\n        .setOnPermissionRequest(PermissionHandler.APPROVE_ALL)\n        .setHooks(hooks)\n).get();\n```\n\n</div>\n\n</div>\n\n### 添加项目上下文\n\n```typescript\nconst session = await client.createSession({\n  hooks: {\n    onUserPromptSubmitted: async (input) => {\n      const projectInfo = await getProjectInfo();\n      \n      return {\n        additionalContext: `\nProject: ${projectInfo.name}\nLanguage: ${projectInfo.language}\nFramework: ${projectInfo.framework}\n        `.trim(),\n      };\n    },\n  },\n});\n```\n\n### 展开简写命令\n\n```typescript\nconst SHORTCUTS: Record<string, string> = {\n  \"/fix\": \"Please fix the errors in the code\",\n  \"/explain\": \"Please explain this code in detail\",\n  \"/test\": \"Please write unit tests for this code\",\n  \"/refactor\": \"Please refactor this code to improve readability and maintainability\",\n};\n\nconst session = await client.createSession({\n  hooks: {\n    onUserPromptSubmitted: async (input) => {\n      for (const [shortcut, expansion] of Object.entries(SHORTCUTS)) {\n        if (input.prompt.startsWith(shortcut)) {\n          const rest = input.prompt.slice(shortcut.length).trim();\n          return {\n            modifiedPrompt: `${expansion}${rest ? `: ${rest}` : \"\"}`,\n          };\n        }\n      }\n      return null;\n    },\n  },\n});\n```\n\n### 内容筛选\n\n```typescript\nconst BLOCKED_PATTERNS = [\n  /password\\s*[:=]/i,\n  /api[_-]?key\\s*[:=]/i,\n  /secret\\s*[:=]/i,\n];\n\nconst session = await client.createSession({\n  hooks: {\n    onUserPromptSubmitted: async (input) => {\n      for (const pattern of BLOCKED_PATTERNS) {\n        if (pattern.test(input.prompt)) {\n          // Replace the prompt with a warning message\n          return {\n            modifiedPrompt: \"[Content blocked: Please don't include sensitive credentials in your prompts. Use environment variables instead.]\",\n            suppressOutput: true,\n          };\n        }\n      }\n      return null;\n    },\n  },\n});\n```\n\n### 强制实施提示长度限制\n\n```typescript\nconst MAX_PROMPT_LENGTH = 10000;\n\nconst session = await client.createSession({\n  hooks: {\n    onUserPromptSubmitted: async (input) => {\n      if (input.prompt.length > MAX_PROMPT_LENGTH) {\n        // Truncate the prompt and add context\n        return {\n          modifiedPrompt: input.prompt.substring(0, MAX_PROMPT_LENGTH),\n          additionalContext: `Note: The original prompt was ${input.prompt.length} characters and was truncated to ${MAX_PROMPT_LENGTH} characters.`,\n        };\n      }\n      return null;\n    },\n  },\n});\n```\n\n### 添加用户首选项\n\n```typescript\ninterface UserPreferences {\n  codeStyle: \"concise\" | \"verbose\";\n  preferredLanguage: string;\n  experienceLevel: \"beginner\" | \"intermediate\" | \"expert\";\n}\n\nconst session = await client.createSession({\n  hooks: {\n    onUserPromptSubmitted: async (input) => {\n      const prefs: UserPreferences = await loadUserPreferences();\n      \n      const contextParts = [];\n      \n      if (prefs.codeStyle === \"concise\") {\n        contextParts.push(\"User prefers concise code with minimal comments.\");\n      } else {\n        contextParts.push(\"User prefers verbose code with detailed comments.\");\n      }\n      \n      if (prefs.experienceLevel === \"beginner\") {\n        contextParts.push(\"Explain concepts in simple terms.\");\n      }\n      \n      return {\n        additionalContext: contextParts.join(\" \"),\n      };\n    },\n  },\n});\n```\n\n### 使用阈值通知\n\n```typescript\nconst promptTimestamps: number[] = [];\nconst NOTICE_THRESHOLD = 10; // prompts\nconst RATE_WINDOW = 60000; // 1 minute\n\nconst session = await client.createSession({\n  hooks: {\n    onUserPromptSubmitted: async (input) => {\n      const now = Date.now();\n      \n      // Remove timestamps outside the window\n      while (promptTimestamps.length > 0 && promptTimestamps[0] < now - RATE_WINDOW) {\n        promptTimestamps.shift();\n      }\n\n      promptTimestamps.push(now);\n      if (promptTimestamps.length >= NOTICE_THRESHOLD) {\n        // This is advisory context for the model, not an enforced rate limit.\n        // Enforce hard limits before calling session.send().\n        return {\n          additionalContext: `The user has sent ${promptTimestamps.length} prompts in the last minute. Suggest waiting before sending more.`,\n        };\n      }\n\n      return null;\n    },\n  },\n});\n```\n\n### 提示模板\n\n```typescript\nconst TEMPLATES: Record<string, (args: string) => string> = {\n  \"bug:\": (desc) => `I found a bug: ${desc}\n\nPlease help me:\n1. Understand why this is happening\n2. Suggest a fix\n3. Explain how to prevent similar bugs`,\n\n  \"feature:\": (desc) => `I want to implement this feature: ${desc}\n\nPlease:\n1. Outline the implementation approach\n2. Identify potential challenges\n3. Provide sample code`,\n};\n\nconst session = await client.createSession({\n  hooks: {\n    onUserPromptSubmitted: async (input) => {\n      for (const [prefix, template] of Object.entries(TEMPLATES)) {\n        if (input.prompt.toLowerCase().startsWith(prefix)) {\n          const args = input.prompt.slice(prefix.length).trim();\n          return {\n            modifiedPrompt: template(args),\n          };\n        }\n      }\n      return null;\n    },\n  },\n});\n```\n\n## 最佳做法\n\n1. **保留用户意向** - 修改提示时，请确保核心意向保持清晰。\n\n2. **对修改保持透明** - 如果显著更改提示，请考虑记录或通知用户。\n\n3. **使用 `additionalContext` 而不是 `modifiedPrompt`** - 添加上下文比重写提示的干扰更小。\n\n4. **将 `additionalContext` 用于提供建议性指导**：此钩子不能拒绝提示词或强制执行策略。 在调用 `session.send()`之前强制实施硬性限制。\n\n5. **保持快速处理** - 此挂钩在每个用户消息上运行。 避免缓慢的操作。\n\n## 另见\n\n* [使用挂钩](/zh/copilot/how-tos/copilot-sdk/hooks)\n* [会话生命周期挂钩](/zh/copilot/how-tos/copilot-sdk/hooks/session-lifecycle)\n* [工具使用前挂钩](/zh/copilot/how-tos/copilot-sdk/hooks/pre-tool-use)"}