Skip to content
5 changes: 5 additions & 0 deletions apps/daemon/src/routes/memory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -588,6 +588,10 @@ export function registerMemoryRoutes(app: Express, ctx: RegisterMemoryRoutesDeps
};
}
}
const chatModel =
typeof body.chatModel === 'string' && body.chatModel.trim()
? body.chatModel.trim()
: '';
let attemptedLLM = false;
if (userMessage.trim().length > 0 && hasAssistant) {
attemptedLLM = true;
Expand All @@ -600,6 +604,7 @@ export function registerMemoryRoutes(app: Express, ctx: RegisterMemoryRoutesDeps
projectRoot: PROJECT_ROOT,
chatAgentId: null,
chatProvider,
...(chatModel ? { chatModel } : {}),
},
),
)
Expand Down
4 changes: 4 additions & 0 deletions apps/daemon/src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6161,6 +6161,10 @@ export async function startServer({
projectRoot: PROJECT_ROOT,
chatAgentId: typeof agentId === 'string' ? agentId : null,
chatModel: typeof safeModel === 'string' ? safeModel : null,
// Forward the per-call BYOK provider snapshot so pickProvider()
// can run "Same as chat" extraction against the user's actual
// provider/endpoint/model instead of falling back to defaults.
chatProvider: byokProvider ?? null,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new handoff still passes the wrong provider shape into the actual post-turn extraction. Here, byokProvider is the ByokChatProviderConfig from the chat request, whose discriminator is protocol; pickProvider() only enters its BYOK branch when chatProvider.provider exists and is a key in PROVIDER_DEFAULTS. Consequently a MiniMax/OpenAI-compatible run reaches this line with { protocol: 'openai', ... }, the snapshot is ignored, and extraction can still fall through to the legacy OpenAI/default path and select gpt-4o-mini—the production failure this PR is intended to fix. Please normalize the request contract before passing it, for example to { provider: byokProvider.protocol, apiKey: byokProvider.apiKey, baseUrl: byokProvider.baseUrl, apiVersion: byokProvider.apiVersion, model: byokProvider.model ?? safeModel }, or update pickProvider() to consume ByokChatProviderConfig consistently. Please also add a close-handler/provider-selection regression test that proves an OpenAI-compatible custom base URL selects the supplied endpoint and model; the current focused tests pass because none exercises this new handoff.

🔁 Powered by Looper · runner=reviewer · agent=codex · An autonomous AI dev team for your GitHub repos.

// Scope the extractor's duplicate-turn de-dup to this conversation, so a
// re-fired turn collapses but an identical (message, reply) in another
// conversation is still examined.
Expand Down
2 changes: 2 additions & 0 deletions apps/web/src/components/ProjectView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1239,6 +1239,7 @@ function byokOpenCodeProviderFromConfig(
protocol: config.apiProtocol,
apiKey: config.apiKey.trim(),
baseUrl: config.baseUrl,
model: config.model,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This field fixes the runtime wiring, but it also changes the BYOK provider payload shape that the web run-isolation tests lock down. The live Web workspace tests job on this head is failing in tests/components/ProjectView.run-isolation.test.tsx at the BYOK request assertions around lines 1531 and 1564: the actual calls now include byokProvider.model (llama3.2 / model), while the expected payloads still omit it. Because Validate workspace fails only through that web test failure, the PR cannot merge until the test contract matches this intentional shape change. Please update those expectations to include the selected model, or narrow the assertions if the exact nested provider object is not meant to be the contract.

🔁 Powered by Looper · runner=reviewer · agent=codex · An autonomous AI dev team for your GitHub repos.

...(selectedProvider?.requiresApiKey === false ? { requiresApiKey: false } : {}),
apiVersion:
config.apiProtocol === 'azure'
Expand Down Expand Up @@ -5894,6 +5895,7 @@ export function ProjectView({
apiKey: byokOpenCodeProvider.apiKey,
baseUrl: byokOpenCodeProvider.baseUrl,
apiVersion: byokOpenCodeProvider.apiVersion,
model: byokOpenCodeProvider.model,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This new field currently breaks the web typecheck because byokOpenCodeProvider is typed as ByokChatProviderConfig, and that shared contract only exposes protocol, apiKey, baseUrl, apiVersion, and requiresApiKey; it does not include model. CI is failing on this exact line with TS2339: Property 'model' does not exist on type 'ByokChatProviderConfig', so the PR cannot merge as-is even though the runtime intent is right. Please thread the selected chat model from a typed source, for example by adding an optional model field to ByokChatProviderConfig in packages/contracts/src/api/chat.ts and populating it from config.model in byokOpenCodeProviderFromConfig, or by building this memory-only snapshot directly from config.model where the request is made.

🔁 Powered by Looper · runner=reviewer · agent=codex · An autonomous AI dev team for your GitHub repos.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This still does not actually send the selected BYOK chat model. The added field reads byokOpenCodeProvider.model, but byokOpenCodeProvider is created by byokOpenCodeProviderFromConfig() and that factory still returns only protocol, apiKey, baseUrl, requiresApiKey, and apiVersion; it never copies config.model. As a result this property serializes as undefined, memory.ts receives no chatProvider.model, and pickProvider() continues to choose envOverrideModel || explicitModel || defaults.model with explicitModel === '', falling back to gpt-4o-mini for OpenAI-compatible BYOK endpoints like MiniMax. That is the bug this PR is meant to fix. Please populate the provider snapshot from the active chat model, for example by adding model: config.model in byokOpenCodeProviderFromConfig() or by setting model: config.model directly in this memory-only byokChatProvider object.

🔁 Powered by Looper · runner=reviewer · agent=codex · An autonomous AI dev team for your GitHub repos.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This now copies the selected model into the BYOK snapshot, but this snapshot still appears to be attached only to the pre-turn /api/memory/extract call. The request body just below sends userMessage, projectId, conversationId, and chatProvider, but no assistantMessage; in apps/daemon/src/routes/memory.ts, extractWithLLM() only runs when hasAssistant is true. The separate daemon close-handler path in apps/daemon/src/server.ts does have the assistant output, but it only passes chatAgentId: byok-opencode and chatModel; it does not pass this chatProvider, and chatProtocolFromAgentId() does not map byok-opencode to the user's actual BYOK provider/base URL.

That means the model value added here does not seem to reach the post-turn pickProvider() call that was falling back to gpt-4o-mini, so MiniMax/OpenAI-compatible BYOK memory extraction can still take the legacy env/media-config path instead of the user's BYOK endpoint. Please either send a post-turn /api/memory/extract request with both assistantMessage and the same byokChatProvider, or thread the BYOK provider snapshot into the daemon close-handler memoryOptions so pickProvider() receives the provider, base URL, API key, and selected model on the actual LLM extraction pass.

🔁 Powered by Looper · runner=reviewer · agent=codex · An autonomous AI dev team for your GitHub repos.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This now includes the selected BYOK model in the /api/memory/extract snapshot, but this request still appears to run only the pre-turn heuristic path: the JSON body below sends userMessage and chatProvider, but no assistantMessage, while apps/daemon/src/routes/memory.ts only calls extractWithLLM() when hasAssistant is true. The actual post-turn extraction for byok-opencode is queued from the daemon close handler in apps/daemon/src/server.ts, and that path forwards chatAgentId/chatModel but not the byokProvider snapshot; chatProtocolFromAgentId() also does not map byok-opencode to the user's BYOK endpoint. So the model added here may still not reach the main LLM extraction pass that was falling back to gpt-4o-mini for MiniMax/OpenAI-compatible BYOK users.

Please either send a post-turn /api/memory/extract call with both assistantMessage and this same byokChatProvider, or thread the run-scoped byokProvider through the daemon close-handler memoryOptions as chatProvider so pickProvider() receives the provider, base URL, API key, and selected model on the extraction pass that has the assistant reply.

🔁 Powered by Looper · runner=reviewer · agent=codex · An autonomous AI dev team for your GitHub repos.

}
: undefined;
if (userText.length > 0) {
Expand Down
2 changes: 2 additions & 0 deletions apps/web/tests/components/ProjectView.run-isolation.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1719,6 +1719,7 @@ describe('ProjectView conversation run isolation', () => {
baseUrl: 'http://localhost:11434',
requiresApiKey: false,
apiVersion: '',
model: 'llama3.2',
},
model: 'llama3.2',
}));
Expand Down Expand Up @@ -1752,6 +1753,7 @@ describe('ProjectView conversation run isolation', () => {
baseUrl: 'http://127.0.0.1:8000/v1',
requiresApiKey: false,
apiVersion: '',
model: 'model',
},
model: 'model',
}));
Expand Down
7 changes: 7 additions & 0 deletions packages/contracts/src/api/chat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,13 @@ export interface ByokChatProviderConfig {
apiVersion?: string;
/** Explicit run-scoped provider policy for presets that do not require bearer credentials. */
requiresApiKey?: boolean;
/**
* Run-scoped chat model id selected in the chat UI. Forwarded to the daemon
* so BYOK-backed utilities (e.g. memory extraction) can honor the user's
* model picker instead of falling back to a hardcoded default. Optional
* because some presets (e.g. Ollama) infer the model from baseUrl/protocol.
*/
model?: string;
}

export interface ByokMediaDefaults {
Expand Down
Loading