Skip to content

Commit 373961d

Browse files
committed
Merge remote-tracking branch 'upstream/main' into feat/paste-marker
2 parents bb95daf + 5b51f40 commit 373961d

7 files changed

Lines changed: 99 additions & 17 deletions

File tree

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@vegamo/deepcode-cli",
3-
"version": "0.1.23",
3+
"version": "0.1.24",
44
"description": "Deep Code CLI - Vibe coding for the deepseek-v4 model in your terminal",
55
"license": "MIT",
66
"type": "module",

src/session.ts

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -570,9 +570,10 @@ export class SessionManager {
570570
const toolCalls = Array.from(toolCallsByIndex.entries())
571571
.sort(([left], [right]) => left - right)
572572
.map(([, toolCall]) => toolCall);
573+
const normalizedToolCalls = this.normalizeLlmToolCalls(toolCalls);
573574
const message: Record<string, unknown> = { content };
574-
if (toolCalls.length > 0) {
575-
message.tool_calls = toolCalls;
575+
if (normalizedToolCalls) {
576+
message.tool_calls = normalizedToolCalls;
576577
}
577578
if (reasoningContent.length > 0) {
578579
message.reasoning_content = reasoningContent;
@@ -1180,7 +1181,7 @@ ${skillMd}
11801181
const rawContent = message?.content;
11811182
const content = typeof rawContent === "string" ? rawContent : "";
11821183
const rawToolCalls = (message as { tool_calls?: unknown[] } | undefined)?.tool_calls ?? null;
1183-
toolCalls = Array.isArray(rawToolCalls) && rawToolCalls.length > 0 ? rawToolCalls : null;
1184+
toolCalls = this.normalizeLlmToolCalls(rawToolCalls);
11841185
const rawThinking = (message as { reasoning_content?: unknown } | undefined)?.reasoning_content;
11851186
const thinking = typeof rawThinking === "string" ? rawThinking : null;
11861187
const refusal = (message as { refusal?: string } | undefined)?.refusal ?? null;
@@ -1899,6 +1900,33 @@ ${skillMd}
18991900
};
19001901
}
19011902

1903+
private generateToolCallId(): string {
1904+
return crypto.randomBytes(16).toString("hex");
1905+
}
1906+
1907+
private normalizeLlmToolCalls(rawToolCalls: unknown[] | null | undefined): unknown[] | null {
1908+
if (!Array.isArray(rawToolCalls) || rawToolCalls.length === 0) {
1909+
return null;
1910+
}
1911+
1912+
return rawToolCalls.map((toolCall) => {
1913+
if (!toolCall || typeof toolCall !== "object" || Array.isArray(toolCall)) {
1914+
return toolCall;
1915+
}
1916+
1917+
const record = toolCall as Record<string, unknown>;
1918+
const id = typeof record.id === "string" ? record.id.trim() : "";
1919+
if (id) {
1920+
return toolCall;
1921+
}
1922+
1923+
return {
1924+
...record,
1925+
id: this.generateToolCallId(),
1926+
};
1927+
});
1928+
}
1929+
19021930
private buildToolMessage(
19031931
sessionId: string,
19041932
toolCallId: string,

src/tests/session.test.ts

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1565,6 +1565,66 @@ test("Write tool params prefer file_path even when content appears first", () =>
15651565
assert.equal(toolMessage.meta?.paramsMd, filePath);
15661566
});
15671567

1568+
test("LLM tool calls without ids receive generated 32 character ids", async () => {
1569+
const workspace = createTempDir("deepcode-tool-call-id-workspace-");
1570+
const home = createTempDir("deepcode-tool-call-id-home-");
1571+
setHomeDir(home);
1572+
1573+
const filePath = path.join(workspace, "note.txt");
1574+
fs.writeFileSync(filePath, "hello\n", "utf8");
1575+
const plan = "## Task List\n\n- [ ] Inspect current behavior";
1576+
const manager = createMockedClientSessionManager(workspace, [
1577+
{
1578+
choices: [
1579+
{
1580+
message: {
1581+
content: "",
1582+
tool_calls: [
1583+
{
1584+
id: "",
1585+
type: "function",
1586+
function: {
1587+
name: "UpdatePlan",
1588+
arguments: JSON.stringify({ plan, explanation: "Initial plan" }),
1589+
},
1590+
},
1591+
{
1592+
type: "function",
1593+
function: {
1594+
name: "read",
1595+
arguments: JSON.stringify({ file_path: filePath }),
1596+
},
1597+
},
1598+
],
1599+
},
1600+
},
1601+
],
1602+
},
1603+
createChatResponse("done", { prompt_tokens: 1, completion_tokens: 1, total_tokens: 2 }),
1604+
]);
1605+
1606+
const sessionId = await manager.createSession({ text: "inspect note" });
1607+
const assistantMessage = manager
1608+
.listSessionMessages(sessionId)
1609+
.find((message) => message.role === "assistant" && (message.messageParams as any)?.tool_calls);
1610+
const toolCalls = (assistantMessage?.messageParams as { tool_calls?: Array<{ id?: unknown }> } | null)?.tool_calls;
1611+
1612+
assert.equal(toolCalls?.length, 2);
1613+
assert.match(String(toolCalls?.[0]?.id), /^[0-9a-f]{32}$/);
1614+
assert.match(String(toolCalls?.[1]?.id), /^[0-9a-f]{32}$/);
1615+
assert.notEqual(toolCalls?.[0]?.id, toolCalls?.[1]?.id);
1616+
1617+
const toolMessages = manager.listSessionMessages(sessionId).filter((message) => message.role === "tool");
1618+
assert.deepEqual(
1619+
toolMessages.map((message) => (message.messageParams as { tool_call_id?: unknown } | null)?.tool_call_id),
1620+
toolCalls?.map((toolCall) => toolCall.id)
1621+
);
1622+
1623+
const readToolMessage = toolMessages.find((message) => JSON.parse(message.content ?? "{}").name === "read");
1624+
assert.equal((readToolMessage?.meta?.function as { name?: string } | undefined)?.name, "read");
1625+
assert.equal(readToolMessage?.meta?.paramsMd, "note.txt");
1626+
});
1627+
15681628
test("buildOpenAIMessages repairs mixed missing duplicate and orphan tool messages", () => {
15691629
const manager = createSessionManager(process.cwd(), "machine-id-mixed-tool-badcase");
15701630
const assistantMessage = (manager as any).buildAssistantMessage(

src/tests/tool-executor.test.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,9 @@ test("ToolExecutor accepts title-case built-in tool aliases", async () => {
2929
type: "function",
3030
function: {
3131
name: "Read",
32-
arguments: JSON.stringify({ file_path: filePath })
33-
}
34-
}
32+
arguments: JSON.stringify({ file_path: filePath }),
33+
},
34+
},
3535
]);
3636

3737
assert.equal(executions.length, 1);

src/tools/executor.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ const BUILT_IN_TOOL_NAME_ALIASES = new Map<string, string>([
9393
["Bash", "bash"],
9494
["Read", "read"],
9595
["Write", "write"],
96-
["Edit", "edit"]
96+
["Edit", "edit"],
9797
]);
9898

9999
export type ToolCallExecution = {

src/ui/PromptInput.tsx

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -775,13 +775,7 @@ export const PromptInput = React.memo(function PromptInput({
775775
}
776776
if (item.kind === "undo") {
777777
onSubmit({ text: "/undo", imageUrls: [], command: "undo" });
778-
setBuffer(EMPTY_BUFFER);
779-
clearUndoRedoStacks();
780-
setImageUrls([]);
781-
setSelectedSkills([]);
782-
setShowSkillsDropdown(false);
783-
pastesRef.current.clear();
784-
expandedRegionsRef.current.clear();
778+
resetPromptInput();
785779
return;
786780
}
787781
if (item.kind === "mcp") {

0 commit comments

Comments
 (0)