Skip to content

fix(pin): strip the pin command from a bare string input, not just the arrays - #337

Open
dex0shubham wants to merge 1 commit into
teamchong:mainfrom
dex0shubham:fix/pin-strip-string-input
Open

dex0shubham wants to merge 1 commit into
teamchong:mainfrom
dex0shubham:fix/pin-strip-string-input

Conversation

@dex0shubham

Copy link
Copy Markdown
Contributor

The bug

relocateOpenAIPins moves a pin: it appends the tail <system-reminder> block and removes the command line from where the user typed it. pin.ts states that contract in three places, e.g.

A mixed turn keeps its prose and loses only the command lines, so the model is not distracted by configuration in the middle of a request it has to act on.

It strips instructions and every system/developer/user entry of items — but items is undefined when /v1/responses is called with a bare string input, a shape the function explicitly supports (normalizeOpenAIRequest wraps it as a user turn). That branch only appended:

if (stringInput) {
  req.input = `${req.input as string}\n\n${pinText}`;   // no strip
}

For input: 'explain this function\n@pxpipe pin be concise, no walls of text':

shape prose after relocation
string input explain this function\n@pxpipe pin be concise, no walls of text + the block
array input explain this function + the block
messages explain this function + the block

Why it reaches upstream

transformOpenAIResponses captures the relocated string as originalInputString after relocateOpenAIPins runs and wraps it back into req.input verbatim when it rebuilds the user item, so nothing downstream removes it. It also survives the bail path, which returns pinBody. The model is handed the configuration line as part of the request it has to answer — precisely what the MOVE contract exists to prevent — and a pin is copied rather than moved on one of the three wire shapes.

Only mixed turns are affected: a command-only turn is answered locally by pinCommandResponseOpenAI in proxy.ts before any transform runs.

The fix

Strip at the same call site, mirroring stripOpenAIContent on the array path. The empty-remainder branch keeps the request valid if a command-only body is ever reached by another route; given the local short-circuit it is belt-and-braces, not a live path. Happy to drop it if you prefer the narrower one-liner.

Tests

The OpenAI relocation path had no tests, so tests/pin-openai-relocate.test.ts is new: 6 cases covering all three input shapes, instructions + string input together, a no-pin request left untouched, and an assistant turn quoting the syntax (foldPins reads user turns only, so stripping it would edit the model's own words out of history).

Against the unfixed pin.ts exactly the 2 string-input cases fail and the 4 controls pass unchanged. Full suite: 86 files / 1299 tests green, tsc --noEmit clean.

…e arrays

relocateOpenAIPins moves a pin: it appends the tail block and removes the
command line from the source. It removes it from `instructions` and from every
system/developer/user item in `items` -- but `items` is undefined when
/v1/responses is called with a bare string `input`, a shape the function
explicitly supports (normalizeOpenAIRequest wraps it as a user turn). That
branch only appended, so the command stayed in the prose.

For input 'explain this function\n@pxpipe pin be concise':

  string input    'explain this function\n@pxpipe pin be concise\n\n<reminder>..'
  array input     'explain this function'                          + the block
  chat messages   'explain this function'                          + the block

It reaches upstream. transformOpenAIResponses captures the relocated string as
originalInputString and wraps it back into req.input verbatim, so nothing
downstream removes it: the model is handed configuration in the middle of the
request it has to answer, which is what the MOVE contract exists to prevent.

Strip at the same call site, mirroring stripOpenAIContent on the array path.
A command-only turn is already answered locally by pinCommandResponseOpenAI
before any transform runs, so the empty-remainder branch is belt-and-braces
rather than a live path -- it keeps the request valid if the block is ever
reached another way.

The OpenAI relocation path had no tests. The 6 new cases cover all three input
shapes, the instructions+string combination, a no-pin request left untouched,
and an assistant turn quoting the syntax (foldPins reads user turns only, so
stripping it would edit the model's own words out of history). Against the
unfixed pin.ts exactly the 2 string-input cases fail and the 4 controls pass.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant