{"meta":{"title":"사용자 프롬프트 제출 후크","intro":"onUserPromptSubmitted 사용자가 메시지를 제출할 때 후크가 호출됩니다. 이를 사용하여 다음을 수행합니다.","product":"GitHub Copilot","breadcrumbs":[{"href":"/ko/copilot","title":"GitHub Copilot"},{"href":"/ko/copilot/how-tos","title":"방법"},{"href":"/ko/copilot/how-tos/copilot-sdk","title":"코필로트 SDK"},{"href":"/ko/copilot/how-tos/copilot-sdk/hooks","title":"후크 사용"},{"href":"/ko/copilot/how-tos/copilot-sdk/hooks/user-prompt-submitted","title":"사용자 프롬프트 제출됨"}],"documentType":"article"},"body":"# 사용자 프롬프트 제출 후크\n\nonUserPromptSubmitted 사용자가 메시지를 제출할 때 후크가 호출됩니다. 이를 사용하여 다음을 수행합니다.\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| Field       | Type   | 설명                     |\n| ----------- | ------ | ---------------------- |\n| `timestamp` | number | 후크가 트리거될 때의 Unix 타임스탬프 |\n| `cwd`       | string | 현재 작업 디렉터리             |\n| `prompt`    | string | 사용자가 제출한 프롬프트          |\n\n## 출력\n\n`null` 또는 `undefined`를 반환하여 프롬프트를 변경하지 않고 사용합니다. 그렇지 않으면 다음 필드가 있는 개체를 반환합니다.\n\n| Field               | Type    | 설명                            |\n| ------------------- | ------- | ----------------------------- |\n| `modifiedPrompt`    | string  | 원래 대신 사용할 수정된 프롬프트            |\n| `additionalContext` | string  | 대화에 추가된 추가 컨텍스트               |\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. \\*\\*\n   `additionalContext`보다 `modifiedPrompt`을(를) 사용\\*\\* - 맥락을 추가하는 것이 프롬프트를 다시 작성하는 것보다 부담이 적습니다.\n\n4. **권고 지침에 사용`additionalContext`**: 이 후크는 프롬프트를 거부하거나 정책을 적용할 수 없습니다.\n   `session.send()`를 호출하기 전에 하드 제한을 적용하세요.\n\n5. **빠른 처리 유지** - 이 후크는 모든 사용자 메시지에서 실행됩니다. 느린 작업을 방지합니다.\n\n## 참고하십시오\n\n* [후크 사용](/ko/copilot/how-tos/copilot-sdk/hooks)\n* [세션 수명 주기 후크](/ko/copilot/how-tos/copilot-sdk/hooks/session-lifecycle)\n* [사전 도구 사용 후크](/ko/copilot/how-tos/copilot-sdk/hooks/pre-tool-use)"}