Repository navigation
fix(codemode): let search be awaited or chained like a Promise - #59981
Conversation
There was a problem hiding this comment.
Looks good. I reproduced both failures on the base: search(q).then/.catch threw "is not a function", and tools.search(q).items threw "un-awaited Promise". With this PR, every call style in the table works. Awaiting, structuredClone, passing the result to a tool and returning it from an async function all behave the same as on the base. Invalid input still throws at the call, and tools.search(bad).catch now throws instead of catching, as the description says. packages/codemode tests pass (1469 pass, 0 fail), packages/core Code Mode tests pass, and both packages type-check (tsc).
Public API is unchanged; this only affects Code Mode internals and the prompt text.
One side effect, not blocking: programs can now see the change through Object.getPrototypeOf(result) === Object.prototype (now false) and typeof result.then (now "function"). That seems acceptable for a search result.
2f4f343 to
dcb7271
Compare
Issue for this PR
Follow-up to #50417
Type of change
What does this PR do?
Problem. In Code Mode,
search(...)returns its result directly while every catalog tool returns a Promise. Models often treatsearchlike the others.await search(...)already worked, butsearch(...).then(...),.catch(...)and.finally(...)threw "is not a function", and DeepSeek spiraled on that. #50417 worked around it with a prompt sentence telling modelssearchis synchronous.Change. I made the interpreter accept every style, then removed the sentence from the prompt.
search(q).itemsawait search(q)search(q).then/catch/finally(...)Promise.all([search(a), search(b)])How it works: the search result keeps its plain data, but its prototype gets non-enumerable
then,catchandfinally. They resolve a real Promise with a plain copy of the result, soawaitstops after one step instead of unwrapping forever.JSON.stringify,
Object.keys, spread, for-in,console.logand returning the result all look the same as before. This lives in@opencode/codemodeand only applies tosearch, not catalog tools.Errors. Bad input (for example
search({ limit: "ten" })) still throws at the call, same as today. Sosearch(q).itemsandtry { await search(q) } catchboth see the error, but a chained.catch(...)does not, because the throw happens before.catchruns. Routing errors into.catchwould need search to return a rejected Promise on failure, which breaks the sync style (try { search(bad) } catchwould stop catching, and property reads would need special cases). Search input is all optional, so this path is rare.Prompt. I removed "
searchis synchronous. Call it withoutawait; it does not return a Promise." frompackages/core/src/codemode/instructions.ts.searchSignaturealready shows a plain return type, and nothing else in the prompt says search is synchronous.Prompt cache. The Code Mode block is re-rendered per request, so existing sessions miss the prompt cache once after deploy. #57627 changes the same prompt. Whichever merges second needs a trivial rebase, and if they ship in the same release the two changes share that one cache miss.
Out of scope. Catalog tool calls and
tools.search(...)are unchanged. They still return real Promises.How did you verify your code works?
packages/codemode:bun typecheckpasses,bun test1472 pass, 0 fail. New tests intest/tool-paths.test.tscover every call style above, check that data views don't show the methods, and check that invalid input still throws. Two older tests usedsearch(...).catchas their example of a missing method, so I switched them to.filter.packages/core:bun typecheckpasses.bun test test/codemode: 23 pass.Probe over OpenRouter with the real rendered Code Mode prompt (partial catalog, 60 tools), the real
executedescription and the real interpreter. 5 tasks x 6 reps per arm, up to 5 executes per trial. Arms: old prompt + old runtime, new prompt + old runtime, new prompt + new runtime.await search.then/.catchWithout the sentence DeepSeek goes back to
await search(...), which is harmless. No trial chained.thenor.catchand no execute failed on search in any arm, so the probe didn't reproduce the original chaining case. The runtime change covers it when it happens. The Gemma misses all hit the execute cap on a two-step task and have nothing to do with search. Gemma's failed executes are guessed tool names before searching, and that rate didn't change.Checklist