# 사용자 프롬프트 제출 후크

onUserPromptSubmitted 사용자가 메시지를 제출할 때 후크가 호출됩니다. 이를 사용하여 다음을 수행합니다.

<!-- markdownlint-disable GHD046 GHD005 -->

<!-- Suppressed: GHD046 (outdated release terminology), GHD005 (hardcoded data variable) -->

* 사용자 프롬프트 수정 또는 향상
* 처리하기 전에 컨텍스트 추가
* 사용자 입력 필터링 또는 유효성 검사
* 프롬프트 템플릿 구현

## 후크 서명

<div class="ghd-codetabs">
<div class="ghd-codetab" data-lang="typescript" data-label="TypeScript"><div class="ghd-codetab-fallback-label" role="heading" aria-level="3">TypeScript</div>

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

```typescript
type UserPromptSubmittedHandler = (
  input: UserPromptSubmittedHookInput,
  invocation: HookInvocation
) => Promise<UserPromptSubmittedHookOutput | null | undefined>;
```

</div>

<div class="ghd-codetab" data-lang="python" data-label="Python"><div class="ghd-codetab-fallback-label" role="heading" aria-level="3">Python</div>

```python
from copilot.session import UserPromptSubmittedHookInput, UserPromptSubmittedHookOutput
from typing import Callable, Awaitable

UserPromptSubmittedHandler = Callable[
    [UserPromptSubmittedHookInput, dict[str, str]],
    Awaitable[UserPromptSubmittedHookOutput | None]
]
```

```python
UserPromptSubmittedHandler = Callable[
    [UserPromptSubmittedHookInput, dict[str, str]],
    Awaitable[UserPromptSubmittedHookOutput | None]
]
```

</div>

<div class="ghd-codetab" data-lang="go" data-label="Go"><div class="ghd-codetab-fallback-label" role="heading" aria-level="3">Go</div>

```golang
package main

import copilot "github.com/github/copilot-sdk/go"

type UserPromptSubmittedHandler func(
    input copilot.UserPromptSubmittedHookInput,
    invocation copilot.HookInvocation,
) (*copilot.UserPromptSubmittedHookOutput, error)

func main() {}
```

```golang
type UserPromptSubmittedHandler func(
    input UserPromptSubmittedHookInput,
    invocation HookInvocation,
) (*UserPromptSubmittedHookOutput, error)
```

</div>

<div class="ghd-codetab" data-lang="dotnet" data-label=".NET"><div class="ghd-codetab-fallback-label" role="heading" aria-level="3">.NET</div>

```csharp
using GitHub.Copilot;

public delegate Task<UserPromptSubmittedHookOutput?> UserPromptSubmittedHandler(
    UserPromptSubmittedHookInput input,
    HookInvocation invocation);
```

```csharp
public delegate Task<UserPromptSubmittedHookOutput?> UserPromptSubmittedHandler(
    UserPromptSubmittedHookInput input,
    HookInvocation invocation);
```

</div>

<div class="ghd-codetab" data-lang="java" data-label="Java"><div class="ghd-codetab-fallback-label" role="heading" aria-level="3">Java</div>

```java
import com.github.copilot.rpc.*;
import java.util.concurrent.CompletableFuture;

public class UserPromptSubmittedSignature {
    UserPromptSubmittedHandler handler = (UserPromptSubmittedHookInput input, HookInvocation invocation) ->
        CompletableFuture.completedFuture(null);
    public static void main(String[] args) {}
}
```

```java
@FunctionalInterface
public interface UserPromptSubmittedHandler {
    CompletableFuture<UserPromptSubmittedHookOutput> handle(
        UserPromptSubmittedHookInput input,
        HookInvocation invocation);
}
```

</div>

</div>

## 입력

| Field       | Type   | 설명                     |
| ----------- | ------ | ---------------------- |
| `timestamp` | number | 후크가 트리거될 때의 Unix 타임스탬프 |
| `cwd`       | string | 현재 작업 디렉터리             |
| `prompt`    | string | 사용자가 제출한 프롬프트          |

## 출력

`null` 또는 `undefined`를 반환하여 프롬프트를 변경하지 않고 사용합니다. 그렇지 않으면 다음 필드가 있는 개체를 반환합니다.

| Field               | Type    | 설명                            |
| ------------------- | ------- | ----------------------------- |
| `modifiedPrompt`    | string  | 원래 대신 사용할 수정된 프롬프트            |
| `additionalContext` | string  | 대화에 추가된 추가 컨텍스트               |
| `suppressOutput`    | boolean | true이면 도우미의 응답 출력을 표시하지 않습니다. |

## 예제

### 모든 사용자 프롬프트 기록

<div class="ghd-codetabs">
<div class="ghd-codetab" data-lang="typescript" data-label="TypeScript"><div class="ghd-codetab-fallback-label" role="heading" aria-level="3">TypeScript</div>

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

</div>

<div class="ghd-codetab" data-lang="python" data-label="Python"><div class="ghd-codetab-fallback-label" role="heading" aria-level="3">Python</div>

```python
from copilot.session import PermissionHandler

async def on_user_prompt_submitted(input_data, invocation):
    print(f"[{invocation['session_id']}] User: {input_data['prompt']}")
    return None

session = await client.create_session(on_permission_request=PermissionHandler.approve_all, hooks={"on_user_prompt_submitted": on_user_prompt_submitted})
```

</div>

<div class="ghd-codetab" data-lang="go" data-label="Go"><div class="ghd-codetab-fallback-label" role="heading" aria-level="3">Go</div>

```golang
package main

import (
    "context"
    "fmt"
    copilot "github.com/github/copilot-sdk/go"
)

func main() {
    client := copilot.NewClient(nil)
    session, _ := client.CreateSession(context.Background(), &copilot.SessionConfig{
        OnPermissionRequest: copilot.PermissionHandler.ApproveAll,
        Hooks: &copilot.SessionHooks{
            OnUserPromptSubmitted: func(input copilot.UserPromptSubmittedHookInput, inv copilot.HookInvocation) (*copilot.UserPromptSubmittedHookOutput, error) {
                fmt.Printf("[%s] User: %s\n", inv.SessionID, input.Prompt)
                return nil, nil
            },
        },
    })
    _ = session
}
```

```golang
session, _ := client.CreateSession(context.Background(), &copilot.SessionConfig{
    Hooks: &copilot.SessionHooks{
        OnUserPromptSubmitted: func(input copilot.UserPromptSubmittedHookInput, inv copilot.HookInvocation) (*copilot.UserPromptSubmittedHookOutput, error) {
            fmt.Printf("[%s] User: %s\n", inv.SessionID, input.Prompt)
            return nil, nil
        },
    },
})
```

</div>

<div class="ghd-codetab" data-lang="dotnet" data-label=".NET"><div class="ghd-codetab-fallback-label" role="heading" aria-level="3">.NET</div>

```csharp
using GitHub.Copilot;

public static class UserPromptSubmittedExample
{
    public static async Task Main()
    {
        await using var client = new CopilotClient();
        var session = await client.CreateSessionAsync(new SessionConfig
        {
            Hooks = new SessionHooks
            {
                OnUserPromptSubmitted = (input, invocation) =>
                {
                    Console.WriteLine($"[{invocation.SessionId}] User: {input.Prompt}");
                    return Task.FromResult<UserPromptSubmittedHookOutput?>(null);
                },
            },
        });
    }
}
```

```csharp
var session = await client.CreateSessionAsync(new SessionConfig
{
    Hooks = new SessionHooks
    {
        OnUserPromptSubmitted = (input, invocation) =>
        {
            Console.WriteLine($"[{invocation.SessionId}] User: {input.Prompt}");
            return Task.FromResult<UserPromptSubmittedHookOutput?>(null);
        },
    },
});
```

</div>

<div class="ghd-codetab" data-lang="java" data-label="Java"><div class="ghd-codetab-fallback-label" role="heading" aria-level="3">Java</div>

<!-- docs-validate: skip -->

```java
import com.github.copilot.*;
import com.github.copilot.rpc.*;
import java.util.concurrent.CompletableFuture;

var hooks = new SessionHooks()
    .setOnUserPromptSubmitted((input, invocation) -> {
        System.out.println("[" + invocation.getSessionId() + "] User: " + input.prompt());
        return CompletableFuture.completedFuture(null);
    });

var session = client.createSession(
    new SessionConfig()
        .setOnPermissionRequest(PermissionHandler.APPROVE_ALL)
        .setHooks(hooks)
).get();
```

</div>

</div>

### 프로젝트 컨텍스트 추가

```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)) {
          // Replace the prompt with a warning message
          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}

Please help me:
1. Understand why this is happening
2. Suggest a fix
3. Explain how to prevent similar bugs`,

  "feature:": (desc) => `I want to implement this feature: ${desc}

Please:
1. Outline the implementation approach
2. Identify potential challenges
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;
    },
  },
});
```

## 모범 사례

1. **사용자 의도 유지** - 프롬프트를 수정할 때 핵심 의도가 명확하게 유지되는지 확인합니다.

2. **수정 내용에 대해 투명하게** - 프롬프트를 크게 변경하는 경우 로깅 또는 사용자에게 알리는 것이 좋습니다.

3. \*\*
   `additionalContext`보다 `modifiedPrompt`을(를) 사용\*\* - 맥락을 추가하는 것이 프롬프트를 다시 작성하는 것보다 부담이 적습니다.

4. **명확한 거부 이유 제공** - 프롬프트를 거부할 때 이유와 해결 방법을 설명합니다.

5. **빠른 처리 유지** - 이 후크는 모든 사용자 메시지에서 실행됩니다. 느린 작업을 방지합니다.

## 참고하십시오

* [후크 사용](/ko/copilot/how-tos/copilot-sdk/hooks)
* [세션 수명 주기 후크](/ko/copilot/how-tos/copilot-sdk/hooks/session-lifecycle)
* [사전 도구 사용 후크](/ko/copilot/how-tos/copilot-sdk/hooks/pre-tool-use)