@gendive/chatllm 0.15.1 → 0.15.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -328,6 +328,10 @@ interface ChatToolDefinition {
328
328
  name: string;
329
329
  description: string;
330
330
  parameters: Record<string, ChatToolParameter>;
331
+ /** @Todo vibecode - UI 표시용 라벨 (미제공 시 name 사용) */
332
+ label?: string;
333
+ /** @Todo vibecode - UI 표시용 아이콘 ('image', 'search', 'code', 'file') */
334
+ icon?: string;
331
335
  }
332
336
  interface ChatMessage {
333
337
  id: string;
@@ -1531,11 +1535,11 @@ declare const FileContentCard: React$1.FC<FileContentCardProps>;
1531
1535
 
1532
1536
  /**
1533
1537
  * @description 도구 정의 배열을 스킬 레코드로 변환
1534
- * @Todo vibecode - 각 tool을 trigger: 'auto' SkillConfig로 변환
1538
+ * @Todo vibecode - 각 tool을 trigger: 'both' SkillConfig로 변환
1535
1539
  *
1536
1540
  * 변환 규칙:
1537
1541
  * - skill name = tool.name (접두사 없음)
1538
- * - trigger: 'auto' (AI 자동 호출)
1542
+ * - trigger: 'both' (AI 자동 호출 + '+' 메뉴에서 수동 선택)
1539
1543
  * - execute: onToolCall 호출 → SkillExecutionResult 반환
1540
1544
  * - metadata.__toolResult__: true (도구 결과 식별용)
1541
1545
  */
@@ -328,6 +328,10 @@ interface ChatToolDefinition {
328
328
  name: string;
329
329
  description: string;
330
330
  parameters: Record<string, ChatToolParameter>;
331
+ /** @Todo vibecode - UI 표시용 라벨 (미제공 시 name 사용) */
332
+ label?: string;
333
+ /** @Todo vibecode - UI 표시용 아이콘 ('image', 'search', 'code', 'file') */
334
+ icon?: string;
331
335
  }
332
336
  interface ChatMessage {
333
337
  id: string;
@@ -1531,11 +1535,11 @@ declare const FileContentCard: React$1.FC<FileContentCardProps>;
1531
1535
 
1532
1536
  /**
1533
1537
  * @description 도구 정의 배열을 스킬 레코드로 변환
1534
- * @Todo vibecode - 각 tool을 trigger: 'auto' SkillConfig로 변환
1538
+ * @Todo vibecode - 각 tool을 trigger: 'both' SkillConfig로 변환
1535
1539
  *
1536
1540
  * 변환 규칙:
1537
1541
  * - skill name = tool.name (접두사 없음)
1538
- * - trigger: 'auto' (AI 자동 호출)
1542
+ * - trigger: 'both' (AI 자동 호출 + '+' 메뉴에서 수동 선택)
1539
1543
  * - execute: onToolCall 호출 → SkillExecutionResult 반환
1540
1544
  * - metadata.__toolResult__: true (도구 결과 식별용)
1541
1545
  */
@@ -979,21 +979,53 @@ var useSkills = (options) => {
979
979
  );
980
980
  if (autoSkills.length === 0) return "";
981
981
  const skillDescriptions = autoSkills.map(([name, config]) => {
982
- const paramsDesc = config.parameters ? Object.entries(config.parameters.properties).map(([k, v]) => `${k}: ${v.description || v.type}`).join(", ") : "";
983
- return `- ${name}: ${config.description}${paramsDesc ? ` (\uD30C\uB77C\uBBF8\uD130: ${paramsDesc})` : ""}`;
984
- }).join("\n");
985
- return `## \uC0AC\uC6A9 \uAC00\uB2A5\uD55C \uC2A4\uD0AC
982
+ let desc = `### ${name}
983
+ ${config.description}`;
984
+ if (config.parameters?.properties) {
985
+ desc += "\n\uD30C\uB77C\uBBF8\uD130:";
986
+ const props = config.parameters.properties;
987
+ const requiredList = config.parameters.required || [];
988
+ Object.entries(props).forEach(([key, prop]) => {
989
+ const isRequired = requiredList.includes(key);
990
+ const tag = isRequired ? "(\uD544\uC218)" : "(\uC120\uD0DD)";
991
+ let line = `
992
+ - ${key} ${tag}: ${prop.description || prop.type}`;
993
+ if (prop.enum) {
994
+ line += ` [\uAC00\uB2A5\uD55C \uAC12: ${prop.enum.join(", ")}]`;
995
+ }
996
+ desc += line;
997
+ });
998
+ const exampleParams = {};
999
+ Object.entries(props).forEach(([key, prop]) => {
1000
+ if (prop.enum) {
1001
+ exampleParams[key] = prop.enum[0];
1002
+ } else if (prop.description) {
1003
+ exampleParams[key] = `${prop.description}`;
1004
+ } else {
1005
+ exampleParams[key] = key;
1006
+ }
1007
+ });
1008
+ desc += `
1009
+ \uC608\uC2DC: <skill_use name="${name}">${JSON.stringify(exampleParams)}</skill_use>`;
1010
+ }
1011
+ return desc;
1012
+ }).join("\n\n");
1013
+ return `## \uC0AC\uC6A9 \uAC00\uB2A5\uD55C \uB3C4\uAD6C
1014
+
1015
+ \uC544\uB798 \uB3C4\uAD6C\uB97C \uC801\uADF9\uC801\uC73C\uB85C \uC0AC\uC6A9\uD558\uC138\uC694. \uC0AC\uC6A9\uC790\uC758 \uC694\uCCAD\uC5D0 \uB9DE\uB294 \uB3C4\uAD6C\uAC00 \uC788\uC73C\uBA74 \uBC18\uB4DC\uC2DC \uD638\uCD9C\uD574\uC57C \uD569\uB2C8\uB2E4.
986
1016
 
987
- \uB2E4\uC74C \uC2A4\uD0AC\uC744 \uD544\uC694\uD560 \uB54C \uD638\uCD9C\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4.
988
- \uD638\uCD9C \uD615\uC2DD: <skill_use name="\uC2A4\uD0AC\uC774\uB984">{"param":"value"}</skill_use>
1017
+ \uD638\uCD9C \uD615\uC2DD:
1018
+ <skill_use name="\uB3C4\uAD6C\uC774\uB984">{"\uD30C\uB77C\uBBF8\uD130": "\uAC12"}</skill_use>
989
1019
 
990
1020
  ${skillDescriptions}
991
1021
 
992
- \uADDC\uCE59:
993
- - \uC2A4\uD0AC \uD638\uCD9C\uC774 \uD544\uC694\uD558\uBA74 \uC751\uB2F5\uC5D0 \uC704 \uD0DC\uADF8\uB97C \uD3EC\uD568\uD558\uC138\uC694
994
- - \uD55C \uBC88\uC5D0 \uD558\uB098\uC758 \uC2A4\uD0AC\uB9CC \uD638\uCD9C\uD558\uC138\uC694
995
- - \uC2A4\uD0AC \uD638\uCD9C \uD0DC\uADF8 \uC678\uC5D0 \uC0AC\uC6A9\uC790\uC5D0\uAC8C \uBCF4\uC5EC\uC904 \uC548\uB0B4 \uBA54\uC2DC\uC9C0\uB3C4 \uD568\uAED8 \uC791\uC131\uD558\uC138\uC694
996
- - \uC2A4\uD0AC\uC774 \uC2E4\uD589\uB418\uBA74 \uACB0\uACFC\uAC00 \uC790\uB3D9\uC73C\uB85C \uC804\uB2EC\uB429\uB2C8\uB2E4`;
1022
+ **\uADDC\uCE59:**
1023
+ - \uC774\uBBF8\uC9C0 \uC0DD\uC131/\uADF8\uB9BC/\uC77C\uB7EC\uC2A4\uD2B8 \uC694\uCCAD \u2192 \uBC18\uB4DC\uC2DC generate_image \uB3C4\uAD6C \uD638\uCD9C
1024
+ - \uCD5C\uC2E0 \uC815\uBCF4/\uAC80\uC0C9/\uB274\uC2A4 \uC694\uCCAD \u2192 \uBC18\uB4DC\uC2DC web_search \uB3C4\uAD6C \uD638\uCD9C
1025
+ - \uB3C4\uAD6C \uD638\uCD9C \uC2DC \uC9E7\uC740 \uC548\uB0B4 \uBA54\uC2DC\uC9C0\uB97C \uBA3C\uC800 \uC791\uC131\uD558\uACE0, \uADF8 \uB4A4\uC5D0 skill_use \uD0DC\uADF8\uB97C \uD3EC\uD568\uD558\uC138\uC694
1026
+ - \uD55C \uBC88\uC5D0 \uD558\uB098\uC758 \uB3C4\uAD6C\uB9CC \uD638\uCD9C\uD558\uC138\uC694
1027
+ - \uB3C4\uAD6C \uC5C6\uC774 \uD14D\uC2A4\uD2B8\uB85C\uB9CC \uB2F5\uBCC0\uD558\uC9C0 \uB9C8\uC138\uC694 (\uD574\uB2F9 \uB3C4\uAD6C\uAC00 \uC788\uB294 \uACBD\uC6B0)
1028
+ - \uB3C4\uAD6C\uAC00 \uC2E4\uD589\uB418\uBA74 \uACB0\uACFC\uAC00 \uC790\uB3D9\uC73C\uB85C \uC804\uB2EC\uB429\uB2C8\uB2E4`;
997
1029
  }, [resolvedSkills]);
998
1030
  const handleSkillCall = (0, import_react3.useCallback)(
999
1031
  async (content, callbacks) => {
@@ -1221,8 +1253,9 @@ var convertToolsToSkills = (tools, onToolCall) => {
1221
1253
  for (const tool of tools) {
1222
1254
  skillMap[tool.name] = {
1223
1255
  description: tool.description,
1224
- trigger: "auto",
1225
- label: tool.name,
1256
+ trigger: "both",
1257
+ label: tool.label || tool.name,
1258
+ icon: tool.icon,
1226
1259
  parameters: {
1227
1260
  type: "object",
1228
1261
  properties: Object.fromEntries(
@@ -2096,6 +2129,7 @@ ${currentContextSummary}` },
2096
2129
  const decoder = new TextDecoder();
2097
2130
  let buffer = "";
2098
2131
  let accumulatedContent = "";
2132
+ let skillTagDetected = false;
2099
2133
  while (true) {
2100
2134
  const { done, value } = await reader.read();
2101
2135
  if (done) break;
@@ -2115,6 +2149,12 @@ ${currentContextSummary}` },
2115
2149
  const thinking = parsed.message?.thinking || "";
2116
2150
  if (content2 || thinking) {
2117
2151
  if (content2) accumulatedContent += content2;
2152
+ if (!skipNextSkillParsingRef.current && accumulatedContent.includes("</skill_use>")) {
2153
+ const endIdx = accumulatedContent.indexOf("</skill_use>");
2154
+ accumulatedContent = accumulatedContent.substring(0, endIdx + "</skill_use>".length);
2155
+ skillTagDetected = true;
2156
+ }
2157
+ const displayContent = skillTagDetected ? accumulatedContent : null;
2118
2158
  setSessions(
2119
2159
  (prev) => prev.map((s) => {
2120
2160
  if (s.id === capturedSessionId) {
@@ -2122,6 +2162,9 @@ ${currentContextSummary}` },
2122
2162
  ...s,
2123
2163
  messages: s.messages.map((m) => {
2124
2164
  if (m.id !== assistantMessageId) return m;
2165
+ if (displayContent) {
2166
+ return { ...m, content: displayContent };
2167
+ }
2125
2168
  let newContent = m.content;
2126
2169
  if (thinking) {
2127
2170
  if (!newContent.includes("<thinking>")) {
@@ -2143,10 +2186,12 @@ ${currentContextSummary}` },
2143
2186
  return s;
2144
2187
  })
2145
2188
  );
2189
+ if (skillTagDetected) break;
2146
2190
  }
2147
2191
  } catch {
2148
2192
  }
2149
2193
  }
2194
+ if (skillTagDetected) break;
2150
2195
  }
2151
2196
  if (buffer.trim()) {
2152
2197
  try {
@@ -2938,11 +2983,23 @@ ${currentSession.contextSummary}` },
2938
2983
  */
2939
2984
  manualSkills,
2940
2985
  /**
2941
- * @description manual 스킬 실행
2942
- * @Todo vibecode - 사용자가 UI에서 스킬 선택 호출, input 내용을 query로 전달
2986
+ * @description manual 스킬/도구 선택 핸들러
2987
+ * @Todo vibecode - 도구 스킬은 selectedAction으로 변환, 일반 스킬은 즉시 실행
2943
2988
  */
2944
2989
  executeManualSkill: (skillName) => {
2945
- executeManualSkill(skillName, { query: input });
2990
+ const isToolSkill = tools?.some((t) => t.name === skillName);
2991
+ if (isToolSkill) {
2992
+ const toolConfig = tools.find((t) => t.name === skillName);
2993
+ setSelectedAction({
2994
+ id: `tool_${skillName}`,
2995
+ label: toolConfig.label || toolConfig.name,
2996
+ icon: toolConfig.icon || "sparkling",
2997
+ description: toolConfig.description,
2998
+ systemPrompt: `\uC0AC\uC6A9\uC790\uAC00 "${toolConfig.label || skillName}" \uB3C4\uAD6C\uB97C \uC0AC\uC6A9\uD558\uB824\uACE0 \uD569\uB2C8\uB2E4. \uC0AC\uC6A9\uC790\uC758 \uBA54\uC2DC\uC9C0\uB97C \uBC14\uD0D5\uC73C\uB85C \uBC18\uB4DC\uC2DC <skill_use name="${skillName}"> \uD0DC\uADF8\uB97C \uC0AC\uC6A9\uD558\uC5EC \uC774 \uB3C4\uAD6C\uB97C \uD638\uCD9C\uD558\uC138\uC694. \uB3C4\uAD6C\uB97C \uC0AC\uC6A9\uD558\uC9C0 \uC54A\uACE0 \uD14D\uC2A4\uD2B8\uB85C\uB9CC \uB2F5\uBCC0\uD558\uC9C0 \uB9C8\uC138\uC694.`
2999
+ });
3000
+ } else {
3001
+ executeManualSkill(skillName, { query: input });
3002
+ }
2946
3003
  }
2947
3004
  };
2948
3005
  };