@kenkaiiii/gg-agent 5.69.0 → 5.71.0

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.
package/dist/index.js CHANGED
@@ -36,6 +36,107 @@ function isLocalBackendUrl(baseUrl) {
36
36
  return false;
37
37
  }
38
38
 
39
+ // src/tool-call-hints.ts
40
+ import { resolveToolSchema } from "@kenkaiiii/gg-ai";
41
+ function normalizeName(name) {
42
+ return name.toLowerCase().replace(/[^a-z0-9]/g, "");
43
+ }
44
+ function editDistance(a, b) {
45
+ let beforePrevious = [];
46
+ let previous = Array.from({ length: b.length + 1 }, (_, i) => i);
47
+ for (let i = 1; i <= a.length; i++) {
48
+ const current = [i];
49
+ for (let j = 1; j <= b.length; j++) {
50
+ const cost = a[i - 1] === b[j - 1] ? 0 : 1;
51
+ let distance = Math.min(
52
+ (previous[j] ?? 0) + 1,
53
+ (current[j - 1] ?? 0) + 1,
54
+ (previous[j - 1] ?? 0) + cost
55
+ );
56
+ if (i > 1 && j > 1 && a[i - 1] === b[j - 2] && a[i - 2] === b[j - 1]) {
57
+ distance = Math.min(distance, (beforePrevious[j - 2] ?? 0) + 1);
58
+ }
59
+ current[j] = distance;
60
+ }
61
+ beforePrevious = previous;
62
+ previous = current;
63
+ }
64
+ return previous[b.length] ?? 0;
65
+ }
66
+ function closestName(name, candidates) {
67
+ const target = normalizeName(name);
68
+ if (!target) return void 0;
69
+ const limit = Math.min(3, Math.max(1, Math.floor(target.length / 3)));
70
+ let best;
71
+ for (const candidate of [...candidates].sort()) {
72
+ if (candidate === name) continue;
73
+ const distance = editDistance(target, normalizeName(candidate));
74
+ if (distance > limit) continue;
75
+ if (!best || distance < best.distance) best = { name: candidate, distance };
76
+ }
77
+ return best?.name;
78
+ }
79
+ function unknownToolMessage(name, available) {
80
+ const suggestion = closestName(name, available);
81
+ if (suggestion) return `Unknown tool: ${name}. Did you mean \`${suggestion}\`?`;
82
+ const names = [...available].sort();
83
+ const listed = names.length > 40 ? `${names.slice(0, 40).join(", ")}, \u2026` : names.join(", ");
84
+ const searchHint = available.includes("tool_search") ? " If the capability is not listed, call `tool_search` to load it." : "";
85
+ return `Unknown tool: ${name}. Available tools: ${listed}.${searchHint}`;
86
+ }
87
+ function isRecord(value) {
88
+ return typeof value === "object" && value !== null && !Array.isArray(value);
89
+ }
90
+ function typeLabel(node) {
91
+ if (!isRecord(node)) return "any";
92
+ if (Array.isArray(node.enum)) {
93
+ return node.enum.map((value) => JSON.stringify(value)).join(" | ");
94
+ }
95
+ const variants = node.anyOf ?? node.oneOf;
96
+ if (Array.isArray(variants)) {
97
+ return [...new Set(variants.map(typeLabel))].filter((t) => t !== "null").join(" | ") || "any";
98
+ }
99
+ if (node.type === "array") return `${typeLabel(node.items)}[]`;
100
+ if (Array.isArray(node.type)) {
101
+ return node.type.filter((t) => t !== "null").join(" | ") || "any";
102
+ }
103
+ return typeof node.type === "string" ? node.type : "any";
104
+ }
105
+ function objectShape(tool) {
106
+ let schema;
107
+ try {
108
+ schema = resolveToolSchema(tool);
109
+ } catch {
110
+ return void 0;
111
+ }
112
+ if (!isRecord(schema) || !isRecord(schema.properties)) return void 0;
113
+ const required = new Set(
114
+ Array.isArray(schema.required) ? schema.required.filter((r) => typeof r === "string") : []
115
+ );
116
+ return { properties: schema.properties, required };
117
+ }
118
+ function argumentHints(tool, args) {
119
+ const shape = objectShape(tool);
120
+ if (!shape) return "";
121
+ const names = Object.keys(shape.properties);
122
+ if (names.length === 0) return `Expected arguments: none.`;
123
+ const fields = names.map((name) => {
124
+ const optional = shape.required.has(name) ? "" : "?";
125
+ return `${name}${optional}: ${typeLabel(shape.properties[name])}`;
126
+ });
127
+ const lines = [`Expected arguments: { ${fields.join(", ")} }`];
128
+ if (isRecord(args)) {
129
+ for (const key of Object.keys(args).sort()) {
130
+ if (key in shape.properties) continue;
131
+ const suggestion = closestName(key, names);
132
+ lines.push(
133
+ suggestion ? `Unknown field \`${key}\` \u2014 did you mean \`${suggestion}\`?` : `Unknown field \`${key}\` is not a parameter of this tool.`
134
+ );
135
+ }
136
+ }
137
+ return lines.join("\n");
138
+ }
139
+
39
140
  // src/output-ceiling.ts
40
141
  var TTL_MS = 24 * 60 * 60 * 1e3;
41
142
  var MIN_PLAUSIBLE_CEILING = 256;
@@ -1574,7 +1675,7 @@ async function executeSingleToolCall(toolCall, options, pushEvent) {
1574
1675
  options.seenToolCalls.add(signature);
1575
1676
  }
1576
1677
  if (!tool) {
1577
- resultContent = `Unknown tool: ${toolCall.name}`;
1678
+ resultContent = unknownToolMessage(toolCall.name, [...options.toolMap.keys()]);
1578
1679
  isError = true;
1579
1680
  } else {
1580
1681
  try {
@@ -1630,8 +1731,10 @@ async function executeSingleToolCall(toolCall, options, pushEvent) {
1630
1731
  const failureCount = (options.invalidToolArgumentCounts.get(failureKey) ?? 0) + 1;
1631
1732
  options.invalidToolArgumentCounts.set(failureKey, failureCount);
1632
1733
  invalidArgAttempt = failureCount;
1734
+ const hints = argumentHints(tool, toolCall.args);
1633
1735
  resultContent = `Invalid arguments for tool \`${toolCall.name}\`:
1634
- ` + prettyError + "\nRe-issue the call with each field as the correct type.";
1736
+ ` + prettyError + (hints ? `
1737
+ ${hints}` : "") + "\nRe-issue the call with each field as the correct type.";
1635
1738
  if (failureCount >= 3) {
1636
1739
  const recoverable = Object.keys(toolCall.args ?? {}).length === 0;
1637
1740
  options.markFatalToolArgumentError(