Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

## Unreleased

- Stop replaying completed assistant reasoning traces to Command Code while preserving visible text and completed tool calls in follow-up request history.
- Add `/commandcode-refresh` and `/commandcode-status` commands for safe model-catalog refreshes and redacted diagnostics.
- Bound model discovery to a configurable 10-second timeout so a slow Provider API cannot block pi startup; timed-out discovery uses the validated cache when available.
- Normalize Command Code context overflow failures so pi can auto-compact and retry, while leaving unrelated rate-limit and capacity errors unchanged.
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,8 @@ Open `/model` and select one of the models provided by Command Code. Model avail

Reasoning metadata is enriched only for models whose Command Code effort support is known. Those models register a model-specific `thinkingLevelMap`, so pi and OMP expose only supported levels. A selected supported level is sent as the documented `params.reasoning_effort` field; `off`, unsupported levels, and newly discovered models without metadata do not add reasoning fields to the request. No prompt instructions are injected.

Reasoning blocks from completed assistant turns remain visible in pi's local session, but are not replayed to Command Code in later requests. Only the assistant's user-visible text and completed tool calls are sent back as history. This matches the current Command Code CLI behavior and prevents prior private reasoning traces from interfering with reasoning on follow-up turns.

List Command Code models from the terminal:

```sh
Expand Down
5 changes: 0 additions & 5 deletions src/converters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -174,11 +174,6 @@ export function messagesToCC(messages?: readonly MessageLike[]): unknown[] {
for (const content of recordArray(message.content)) {
if (content.type === "text") {
parts.push({ type: "text", text: stringValue(content.text) ?? "" })
} else if (content.type === "thinking") {
parts.push({
type: "reasoning",
text: stringValue(content.thinking) ?? "",
})
} else if (content.type === "toolCall") {
const toolCallId = stringValue(content.id) ?? ""
if (!pairedToolCallIds.has(toolCallId)) continue
Expand Down
45 changes: 45 additions & 0 deletions tests/test-live-e2e.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,51 @@ try {
assert.equal(reasoning.code, 0, reasoning.stderr)
assert.match(reasoning.stdout, new RegExp(marker))

console.log("[live-e2e] live multi-turn reasoning history")
const multiTurn = await runRpc(extensionPath, async ({ send, waitFor, events, getStderr }) => {
const countThinkingDeltas = (startIndex) =>
events
.slice(startIndex)
.filter(
(event) =>
event.type === "message_update" &&
event.assistantMessageEvent?.type === "thinking_delta" &&
typeof event.assistantMessageEvent.delta === "string" &&
event.assistantMessageEvent.delta.length > 0,
).length

const firstStart = events.length
send({
id: "reasoning-turn-1",
type: "prompt",
message:
"Reason step by step before answering. Calculate 37 * 41, then reply with only the number.",
})
await waitFor(
(event) => event.type === "response" && event.id === "reasoning-turn-1" && event.success,
)
await waitFor((event) => event.type === "agent_settled")
const firstThinkingDeltas = countThinkingDeltas(firstStart)

const secondStart = events.length
send({
id: "reasoning-turn-2",
type: "prompt",
message:
"Now reason step by step again. Add 19 to your previous numeric result, then reply with only the number.",
})
await waitFor(
(event) => event.type === "response" && event.id === "reasoning-turn-2" && event.success,
)
await waitFor((event) => event.type === "agent_settled" && events.indexOf(event) >= secondStart)
const secondThinkingDeltas = countThinkingDeltas(secondStart)

return { firstThinkingDeltas, secondThinkingDeltas, stderr: getStderr() }
})
assert.ok(multiTurn.firstThinkingDeltas > 0, "first turn should stream reasoning")
assert.ok(multiTurn.secondThinkingDeltas > 0, "follow-up turn should stream fresh reasoning")
assert.doesNotMatch(multiTurn.stderr, /Bearer\s+\S+/i)

console.log("[live-e2e] live runtime refresh/status commands")
const runtime = await runRpc(extensionPath, async ({ send, waitFor, getStderr }) => {
send({ id: "commands", type: "get_commands" })
Expand Down
41 changes: 39 additions & 2 deletions tests/test-pure-functions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -499,12 +499,49 @@ describe("messagesToCC()", () => {

assert.equal(objectAt(result, ["0", "role"]), "user")
assert.equal(objectAt(result, ["1", "role"]), "assistant")
assert.equal(objectAt(result, ["1", "content", "0", "type"]), "reasoning")
assert.equal(objectAt(result, ["1", "content", "2", "type"]), "tool-call")
assert.equal(objectAt(result, ["1", "content", "0", "type"]), "text")
assert.equal(objectAt(result, ["1", "content", "1", "type"]), "tool-call")
assert.equal(objectAt(result, ["1", "content", "2"]), undefined)
assert.equal(objectAt(result, ["2", "role"]), "tool")
assert.equal(objectAt(result, ["2", "content", "0", "output", "value"]), "hello\nworld")
})

it("drops previous assistant reasoning while preserving text and tool calls", () => {
const result = messagesToCC([
{ role: "user", content: "first question" },
{
role: "assistant",
content: [
{ type: "thinking", thinking: "private reasoning from turn one" },
{ type: "text", text: "first answer" },
],
},
{ role: "user", content: "follow-up question" },
])

assert.deepEqual(result, [
{ role: "user", content: "first question" },
{ role: "assistant", content: [{ type: "text", text: "first answer" }] },
{ role: "user", content: "follow-up question" },
])
})

it("omits assistant turns that contain only previous reasoning", () => {
const result = messagesToCC([
{ role: "user", content: "first question" },
{
role: "assistant",
content: [{ type: "thinking", thinking: "private reasoning" }],
},
{ role: "user", content: "follow-up question" },
])

assert.deepEqual(result, [
{ role: "user", content: "first question" },
{ role: "user", content: "follow-up question" },
])
})

it("drops orphaned tool calls that have no matching tool result", () => {
const result = messagesToCC([
{ role: "user", content: "edit a file" },
Expand Down
Loading