Skip to content

Commit 2a6b877

Browse files
committed
feat: fix password reset issue
1 parent bd0d3f5 commit 2a6b877

10 files changed

Lines changed: 253 additions & 26 deletions

File tree

apps/api/src/ai-agent/pipeline/3-generation.ts

Lines changed: 181 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,8 @@ export type GenerationResult = {
7272
};
7373
/** Full per-tool call counts from this generation */
7474
toolCallsByName?: Record<string, number>;
75+
/** Per-tool call counts that should be used for credit charging */
76+
chargeableToolCallsByName?: Record<string, number>;
7577
/** Total number of tool calls from this generation */
7678
totalToolCalls?: number;
7779
/** Custom skills explicitly read via loadSkill() in this run */
@@ -140,6 +142,10 @@ const TOOL_METADATA_BY_DEFAULT_SKILL_NAME = new Map(
140142
AI_AGENT_TOOL_CATALOG.map((entry) => [entry.defaultSkill.name, entry])
141143
);
142144
const LOAD_SKILL_TOOL_NAME = "loadSkill";
145+
const BUILT_IN_TOOL_NAME_SET = new Set<string>([
146+
...AI_AGENT_TOOL_CATALOG.map((t) => t.id),
147+
LOAD_SKILL_TOOL_NAME,
148+
]);
143149

144150
export type RuntimeCustomSkillCatalogEntry = {
145151
fileName: string;
@@ -153,8 +159,21 @@ type ToolCallLike = {
153159
toolName?: string;
154160
};
155161

162+
type ToolResultLike = {
163+
toolName?: string;
164+
output?: unknown;
165+
};
166+
167+
type ToolContentPartLike = {
168+
type?: string;
169+
toolName?: string;
170+
output?: unknown;
171+
};
172+
156173
type ToolStepLike = {
157-
toolCalls?: ToolCallLike[];
174+
toolCalls?: readonly ToolCallLike[];
175+
toolResults?: readonly ToolResultLike[];
176+
content?: readonly ToolContentPartLike[];
158177
};
159178

160179
function clampToolInvocationBudget(
@@ -191,7 +210,7 @@ export function getNonFinishToolCallCount(
191210
}
192211

193212
function countNonFinishToolCallsFromSteps(
194-
steps: ToolStepLike[] | undefined
213+
steps: readonly ToolStepLike[] | undefined
195214
): number {
196215
if (!steps || steps.length === 0) {
197216
return 0;
@@ -213,6 +232,128 @@ function countNonFinishToolCallsFromSteps(
213232
return total;
214233
}
215234

235+
function isRecord(value: unknown): value is Record<string, unknown> {
236+
return typeof value === "object" && value !== null && !Array.isArray(value);
237+
}
238+
239+
function isBuiltInToolName(toolName: string): boolean {
240+
return BUILT_IN_TOOL_NAME_SET.has(toolName);
241+
}
242+
243+
function incrementToolCount(
244+
counts: Record<string, number>,
245+
toolName: string
246+
): void {
247+
counts[toolName] = (counts[toolName] ?? 0) + 1;
248+
}
249+
250+
function outputIndicatesToolFailure(output: unknown): boolean {
251+
if (!isRecord(output)) {
252+
return false;
253+
}
254+
255+
if ("success" in output && output.success === false) {
256+
return true;
257+
}
258+
259+
return (
260+
"success" in output &&
261+
output.success !== true &&
262+
typeof output.error === "string" &&
263+
output.error.length > 0
264+
);
265+
}
266+
267+
export function buildFailedBuiltInToolCallsByName(
268+
steps: readonly ToolStepLike[] | undefined
269+
): Record<string, number> {
270+
if (!steps || steps.length === 0) {
271+
return {};
272+
}
273+
274+
const failedByName: Record<string, number> = {};
275+
276+
for (const step of steps) {
277+
const contentParts = Array.isArray(step.content) ? step.content : [];
278+
279+
if (contentParts.length > 0) {
280+
for (const part of contentParts) {
281+
const partType = part?.type;
282+
const toolName = part?.toolName;
283+
284+
if (!(toolName && typeof toolName === "string")) {
285+
continue;
286+
}
287+
if (!isBuiltInToolName(toolName)) {
288+
continue;
289+
}
290+
291+
if (partType === "tool-error") {
292+
incrementToolCount(failedByName, toolName);
293+
continue;
294+
}
295+
296+
if (
297+
partType === "tool-result" &&
298+
outputIndicatesToolFailure(part.output)
299+
) {
300+
incrementToolCount(failedByName, toolName);
301+
}
302+
}
303+
304+
continue;
305+
}
306+
307+
for (const toolResult of step.toolResults ?? []) {
308+
const toolName = toolResult?.toolName;
309+
if (!(toolName && typeof toolName === "string")) {
310+
continue;
311+
}
312+
if (!isBuiltInToolName(toolName)) {
313+
continue;
314+
}
315+
if (outputIndicatesToolFailure(toolResult.output)) {
316+
incrementToolCount(failedByName, toolName);
317+
}
318+
}
319+
}
320+
321+
return failedByName;
322+
}
323+
324+
export function getChargeableToolCallsByName(params: {
325+
toolCallsByName: Record<string, number>;
326+
failedBuiltInToolCallsByName?: Record<string, number>;
327+
}): Record<string, number> {
328+
const failedByName = params.failedBuiltInToolCallsByName ?? {};
329+
const chargeableByName: Record<string, number> = {};
330+
331+
for (const [toolName, rawCount] of Object.entries(params.toolCallsByName)) {
332+
if (!Number.isFinite(rawCount) || rawCount <= 0) {
333+
continue;
334+
}
335+
336+
const attemptedCount = Math.floor(rawCount);
337+
const failedCount = isBuiltInToolName(toolName)
338+
? Math.max(
339+
0,
340+
Math.floor(
341+
Number.isFinite(failedByName[toolName])
342+
? (failedByName[toolName] as number)
343+
: 0
344+
)
345+
)
346+
: 0;
347+
const chargeableCount = Math.max(0, attemptedCount - failedCount);
348+
349+
if (chargeableCount > 0) {
350+
chargeableByName[toolName] = chargeableCount;
351+
}
352+
}
353+
354+
return chargeableByName;
355+
}
356+
216357
function buildStopConditions(params: {
217358
toolBudgetCap: number;
218359
usedNonFinishCallsOffset?: number;
@@ -223,7 +364,7 @@ function buildStopConditions(params: {
223364

224365
return [
225366
...finishToolNames.map((toolName) => hasToolCall(toolName)),
226-
({ steps }: { steps: ToolStepLike[] }) =>
367+
({ steps }: { steps: readonly ToolStepLike[] }) =>
227368
usedNonFinishCallsOffset + countNonFinishToolCallsFromSteps(steps) >=
228369
params.toolBudgetCap,
229370
stepCountIs(params.toolBudgetCap + 2),
@@ -499,7 +640,7 @@ async function generateWithToolLoopAgent(input: {
499640
stopWhen: Array<
500641
| ReturnType<typeof hasToolCall>
501642
| ReturnType<typeof stepCountIs>
502-
| ((params: { steps: ToolStepLike[] }) => boolean)
643+
| ((params: { steps: readonly ToolStepLike[] }) => boolean)
503644
>;
504645
abortSignal?: AbortSignal;
505646
}): Promise<Awaited<ReturnType<ToolLoopAgent["generate"]>>> {
@@ -618,6 +759,7 @@ export async function generate(
618759
sendPrivateMessage: 0,
619760
},
620761
toolCallsByName,
762+
chargeableToolCallsByName: toolCallsByName,
621763
totalToolCalls: getTotalToolCalls(toolCallsByName),
622764
};
623765
}
@@ -717,7 +859,7 @@ export async function generate(
717859
const systemPrompt = buildRuntimeSystemPrompt();
718860
const prepareStep: PrepareStepFunction<ToolSet> = ({ steps }) => {
719861
const nonFinishCallsUsed = countNonFinishToolCallsFromSteps(
720-
(steps as ToolStepLike[] | undefined) ?? []
862+
(steps as readonly ToolStepLike[] | undefined) ?? []
721863
);
722864
const budgetExhausted = nonFinishCallsUsed >= maxToolInvocationsPerRun;
723865

@@ -818,6 +960,7 @@ export async function generate(
818960
sendPrivateMessage: toolContext.counters?.sendPrivateMessage ?? 0,
819961
},
820962
toolCallsByName,
963+
chargeableToolCallsByName: toolCallsByName,
821964
totalToolCalls: getTotalToolCalls(toolCallsByName),
822965
usedCustomSkills: buildUsedCustomSkills(),
823966
};
@@ -839,6 +982,13 @@ export async function generate(
839982
sdkToolCallsByName,
840983
counterToolCallsByName
841984
);
985+
const failedBuiltInToolCallsByName = buildFailedBuiltInToolCallsByName(
986+
result.steps as readonly ToolStepLike[] | undefined
987+
);
988+
const chargeableToolCallsByName = getChargeableToolCallsByName({
989+
toolCallsByName,
990+
failedBuiltInToolCallsByName,
991+
});
842992
const totalToolCalls = getTotalToolCalls(toolCallsByName);
843993
const nonFinishToolCalls = getNonFinishToolCallCount(toolCallsByName);
844994
const remainingNonFinishBudget = Math.max(
@@ -893,6 +1043,7 @@ export async function generate(
8931043
sendPrivateMessage: sendPrivateMessageCallCount,
8941044
},
8951045
toolCallsByName,
1046+
chargeableToolCallsByName,
8961047
totalToolCalls,
8971048
usedCustomSkills: buildUsedCustomSkills(),
8981049
};
@@ -920,6 +1071,7 @@ export async function generate(
9201071
sendPrivateMessage: sendPrivateMessageCallCount,
9211072
},
9221073
toolCallsByName,
1074+
chargeableToolCallsByName,
9231075
totalToolCalls,
9241076
usedCustomSkills: buildUsedCustomSkills(),
9251077
};
@@ -965,7 +1117,7 @@ export async function generate(
9651117
tools: repairTools,
9661118
prepareStep: ({ steps }) => {
9671119
const repairCallsUsed = countNonFinishToolCallsFromSteps(
968-
(steps as ToolStepLike[] | undefined) ?? []
1120+
(steps as readonly ToolStepLike[] | undefined) ?? []
9691121
);
9701122
const totalUsed = nonFinishToolCalls + repairCallsUsed;
9711123
const budgetExhausted = totalUsed >= maxToolInvocationsPerRun;
@@ -998,6 +1150,11 @@ export async function generate(
9981150
toolCallsByName,
9991151
repairAbortToolCallsByName
10001152
);
1153+
const combinedRepairAbortChargeableToolCallsByName =
1154+
getChargeableToolCallsByName({
1155+
toolCallsByName: combinedRepairAbortToolCallsByName,
1156+
failedBuiltInToolCallsByName,
1157+
});
10011158
return {
10021159
decision: {
10031160
action: "skip" as const,
@@ -1011,6 +1168,8 @@ export async function generate(
10111168
combinedRepairAbortToolCallsByName.sendPrivateMessage ?? 0,
10121169
},
10131170
toolCallsByName: combinedRepairAbortToolCallsByName,
1171+
chargeableToolCallsByName:
1172+
combinedRepairAbortChargeableToolCallsByName,
10141173
totalToolCalls: getTotalToolCalls(
10151174
combinedRepairAbortToolCallsByName
10161175
),
@@ -1031,6 +1190,18 @@ export async function generate(
10311190
repairToolCallsByName,
10321191
repairCounterToolCallsByName
10331192
);
1193+
const repairFailedBuiltInToolCallsByName =
1194+
buildFailedBuiltInToolCallsByName(
1195+
repairResult.steps as readonly ToolStepLike[] | undefined
1196+
);
1197+
const combinedFailedBuiltInToolCallsByName = mergeToolCallsByName(
1198+
failedBuiltInToolCallsByName,
1199+
repairFailedBuiltInToolCallsByName
1200+
);
1201+
const repairChargeableToolCallsByName = getChargeableToolCallsByName({
1202+
toolCallsByName: combinedRepairToolCallsByName,
1203+
failedBuiltInToolCallsByName: combinedFailedBuiltInToolCallsByName,
1204+
});
10341205
const repairTotalToolCalls = getTotalToolCalls(
10351206
combinedRepairToolCallsByName
10361207
);
@@ -1062,6 +1233,7 @@ export async function generate(
10621233
sendPrivateMessage: repairSendPrivateMessageCalls,
10631234
},
10641235
toolCallsByName: combinedRepairToolCallsByName,
1236+
chargeableToolCallsByName: repairChargeableToolCallsByName,
10651237
totalToolCalls: repairTotalToolCalls,
10661238
usedCustomSkills: buildUsedCustomSkills(),
10671239
};
@@ -1089,6 +1261,7 @@ export async function generate(
10891261
sendPrivateMessage: repairSendPrivateMessageCalls,
10901262
},
10911263
toolCallsByName: combinedRepairToolCallsByName,
1264+
chargeableToolCallsByName: repairChargeableToolCallsByName,
10921265
totalToolCalls: repairTotalToolCalls,
10931266
usedCustomSkills: buildUsedCustomSkills(),
10941267
};
@@ -1121,6 +1294,7 @@ export async function generate(
11211294
sendPrivateMessage: sendPrivateMessageCallCount,
11221295
},
11231296
toolCallsByName,
1297+
chargeableToolCallsByName,
11241298
totalToolCalls,
11251299
usedCustomSkills: buildUsedCustomSkills(),
11261300
};
@@ -1159,6 +1333,7 @@ export async function generate(
11591333
sendPrivateMessage: sendPrivateMessageCallCount,
11601334
},
11611335
toolCallsByName,
1336+
chargeableToolCallsByName,
11621337
totalToolCalls,
11631338
usedCustomSkills: buildUsedCustomSkills(),
11641339
};

apps/api/src/ai-agent/pipeline/index.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -513,10 +513,12 @@ export async function runAiAgentPipeline(
513513
return;
514514
}
515515

516-
const charge = result?.toolCallsByName
516+
const toolCallsForCredits =
517+
result?.chargeableToolCallsByName ?? result?.toolCallsByName;
518+
const charge = toolCallsForCredits
517519
? calculateAiCreditCharge({
518520
modelId: resolvedModelId,
519-
toolCallsByName: result.toolCallsByName,
521+
toolCallsByName: toolCallsForCredits,
520522
})
521523
: getMinimumAiCreditCharge(resolvedModelId);
522524

apps/api/src/ai-agent/prompts/prompt-generation-a2z.test.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -508,8 +508,8 @@ Acme helps teams manage invoices, recurring billing, and account operations with
508508
509509
The visitor is not identified yet. Ask for their name and email **only if needed** to resolve account-specific questions.
510510
511-
- Ask for name + email when necessary (don't badger).
512-
- After receiving details, call identifyVisitor({ name, email }).
511+
- Ask for email when necessary (don't badger). Ask for name when helpful.
512+
- After receiving details, call identifyVisitor with email. Include name when available.
513513
- Only verify an email if it looks legitimate; if it seems fake, ask for a real email instead.
514514
- If the visitor wants to update their email, use identifyVisitor to update it."
515515
,
@@ -601,8 +601,8 @@ Acme helps teams manage invoices, recurring billing, and account operations with
601601
602602
The visitor is not identified yet. Ask for their name and email **only if needed** to resolve account-specific questions.
603603
604-
- Ask for name + email when necessary (don't badger).
605-
- After receiving details, call identifyVisitor({ name, email }).
604+
- Ask for email when necessary (don't badger). Ask for name when helpful.
605+
- After receiving details, call identifyVisitor with email. Include name when available.
606606
- Only verify an email if it looks legitimate; if it seems fake, ask for a real email instead.
607607
- If the visitor wants to update their email, use identifyVisitor to update it.
608608

0 commit comments

Comments
 (0)