# 用户提示提交挂钩

使用 onUserPromptSubmitted 挂钩修改提示、添加上下文和筛选用户 Copilot SDK输入。

> \[!NOTE]

```
          Copilot SDK 当前处于 公共预览版. 功能和可用性可能会发生更改。
```

用户提交消息时将调用`onUserPromptSubmitted`钩子。 使用它可执行以下操作：

* 修改或增强用户提示
* 在处理之前添加上下文
* 筛选或验证用户输入
* 实现提示模板

## 挂钩签名

```typescript
import type { UserPromptSubmittedHookInput, HookInvocation, UserPromptSubmittedHookOutput } from "@github/copilot-sdk";
type UserPromptSubmittedHandler = (
  input: UserPromptSubmittedHookInput,
  invocation: HookInvocation
) => Promise<
  UserPromptSubmittedHookOutput | null | undefined
>;
```

有关 Python、Go 和 .NET 中的挂钩签名，请参阅 [`github/copilot-sdk` 存储库](https://github.com/github/copilot-sdk/blob/main/docs/hooks/user-prompt-submitted.md#hook-signature)。 有关 Java，请参阅 [`github/copilot-sdk-java` 存储库](https://github.com/github/copilot-sdk-java)。

## 输入

| 领域          | 类型     | 说明              |
| ----------- | ------ | --------------- |
| `timestamp` | number | 触发挂钩时的 Unix 时间戳 |
| `cwd`       | 字符串    | 当前工作目录          |
| `prompt`    | 字符串    | 用户提交的提示信息       |

## 输出

返回 `null` 或 `undefined` 以保持提示不变。 否则，返回具有以下任何字段的对象。

| 领域                  | 类型  | 说明                    |
| ------------------- | --- | --------------------- |
| `modifiedPrompt`    | 字符串 | 使用修改后的提示代替原始提示        |
| `additionalContext` | 字符串 | 向对话添加额外上下文            |
| `suppressOutput`    | 布尔  | 如果为 true，则禁止显示助手的响应输出 |

## 示例

### 记录所有用户提示

```typescript
const session = await client.createSession({
  hooks: {
    onUserPromptSubmitted: async (
      input, invocation
    ) => {
      console.log(
        `[${invocation.sessionId}] `
        + `User: ${input.prompt}`
      );
      return null; // Pass through unchanged
    },
  },
});
```

有关 Python、Go 和 .NET 中的示例，请参阅 [`github/copilot-sdk` 存储库](https://github.com/github/copilot-sdk/blob/main/docs/hooks/user-prompt-submitted.md#log-all-user-prompts)。 有关 Java，请参阅 [`github/copilot-sdk-java` 存储库](https://github.com/github/copilot-sdk-java)。

### 添加项目上下文

```typescript
const session = await client.createSession({
  hooks: {
    onUserPromptSubmitted: async (input) => {
      const projectInfo = await getProjectInfo();

      return {
        additionalContext: `
Project: ${projectInfo.name}
Language: ${projectInfo.language}
Framework: ${projectInfo.framework}
        `.trim(),
      };
    },
  },
});
```

### 展开简写命令

```typescript
const SHORTCUTS: Record<string, string> = {
  "/fix":
    "Please fix the errors in the code",
  "/explain":
    "Please explain this code in detail",
  "/test":
    "Please write unit tests for this code",
  "/refactor":
    "Please refactor this code to improve "
    + "readability and maintainability",
};

const session = await client.createSession({
  hooks: {
    onUserPromptSubmitted: async (input) => {
      for (const [shortcut, expansion]
        of Object.entries(SHORTCUTS)) {
        if (input.prompt.startsWith(shortcut)) {
          const rest = input.prompt
            .slice(shortcut.length).trim();
          return {
            modifiedPrompt:
              `${expansion}`
              + `${rest ? `: ${rest}` : ""}`,
          };
        }
      }
      return null;
    },
  },
});
```

### 内容筛选

```typescript
const BLOCKED_PATTERNS = [
  /password\s*[:=]/i,
  /api[_-]?key\s*[:=]/i,
  /secret\s*[:=]/i,
];

const session = await client.createSession({
  hooks: {
    onUserPromptSubmitted: async (input) => {
      for (const pattern of BLOCKED_PATTERNS) {
        if (pattern.test(input.prompt)) {
          return {
            modifiedPrompt:
              "[Content blocked: Please don't "
              + "include sensitive credentials "
              + "in your prompts. Use environment "
              + "variables instead.]",
            suppressOutput: true,
          };
        }
      }
      return null;
    },
  },
});
```

### 强制提示长度限制

```typescript
const MAX_PROMPT_LENGTH = 10000;

const session = await client.createSession({
  hooks: {
    onUserPromptSubmitted: async (input) => {
      if (input.prompt.length > MAX_PROMPT_LENGTH) {
        // Truncate the prompt and add context
        return {
          modifiedPrompt: input.prompt.substring(0, MAX_PROMPT_LENGTH),
          additionalContext: `Note: The original prompt was ${input.prompt.length} characters and was truncated to ${MAX_PROMPT_LENGTH} characters.`,
        };
      }
      return null;
    },
  },
});
```

### 添加用户首选项

```typescript
interface UserPreferences {
  codeStyle: "concise" | "verbose";
  preferredLanguage: string;
  experienceLevel: "beginner" | "intermediate" | "expert";
}

const session = await client.createSession({
  hooks: {
    onUserPromptSubmitted: async (input) => {
      const prefs: UserPreferences = await loadUserPreferences();
      
      const contextParts = [];
      
      if (prefs.codeStyle === "concise") {
        contextParts.push("User prefers concise code with minimal comments.");
      } else {
        contextParts.push("User prefers verbose code with detailed comments.");
      }
      
      if (prefs.experienceLevel === "beginner") {
        contextParts.push("Explain concepts in simple terms.");
      }
      
      return {
        additionalContext: contextParts.join(" "),
      };
    },
  },
});
```

### 速率限制

```typescript
const promptTimestamps: number[] = [];
const RATE_LIMIT = 10; // prompts
const RATE_WINDOW = 60000; // 1 minute

const session = await client.createSession({
  hooks: {
    onUserPromptSubmitted: async (input) => {
      const now = Date.now();
      
      // Remove timestamps outside the window
      while (promptTimestamps.length > 0 && promptTimestamps[0] < now - RATE_WINDOW) {
        promptTimestamps.shift();
      }
      
      if (promptTimestamps.length >= RATE_LIMIT) {
        return {
          reject: true,
          rejectReason: `Rate limit exceeded. Please wait before sending more prompts.`,
        };
      }
      
      promptTimestamps.push(now);
      return null;
    },
  },
});
```

### 提示模板

```typescript
const TEMPLATES: Record<
  string, (args: string) => string
> = {
  "bug:": (desc) =>
    `I found a bug: ${desc}\n\n`
    + `Please help me:\n`
    + `1. Understand why this is happening\n`
    + `2. Suggest a fix\n`
    + `3. Explain how to prevent similar bugs`,

  "feature:": (desc) =>
    `I want to implement this feature: `
    + `${desc}\n\n`
    + `Please:\n`
    + `1. Outline the implementation approach\n`
    + `2. Identify potential challenges\n`
    + `3. Provide sample code`,
};

const session = await client.createSession({
  hooks: {
    onUserPromptSubmitted: async (input) => {
      for (const [prefix, template]
        of Object.entries(TEMPLATES)) {
        if (
          input.prompt.toLowerCase()
            .startsWith(prefix)
        ) {
          const args = input.prompt
            .slice(prefix.length).trim();
          return {
            modifiedPrompt: template(args),
          };
        }
      }
      return null;
    },
  },
});
```

## 最佳做法

* **保留用户意图。** 修改提示时，请确保核心意向保持清晰。
* **对修改保持透明。** 如果显著更改了提示，请考虑记录或通知用户。
* **使用 `additionalContext` 结束 `modifiedPrompt`。** 添加上下文比重写提示更不具有侵入性。
* **提供明确的拒绝原因。** 拒绝提示时，请解释原因以及如何解决问题。
* **保持处理速度。** 此挂钩在每个用户消息上运行。 避免缓慢的操作。

## 延伸阅读

* [挂钩快速入门](/zh/copilot/how-tos/copilot-sdk/use-hooks/quickstart)
* [会话生命周期挂钩](/zh/copilot/how-tos/copilot-sdk/use-hooks/session-lifecycle)
* [工具使用前挂钩](/zh/copilot/how-tos/copilot-sdk/use-hooks/pre-tool-use)