fix(core): preserve provider metadata namespaces (#35817)

Co-authored-by: Aiden Cline <63023139+rekram1-node@users.noreply.github.com>
Co-authored-by: Aiden Cline <aidenpcline@gmail.com>
This commit is contained in:
opencode-agent[bot] 2026-07-07 21:45:58 -05:00 committed by GitHub
commit ed4f833813
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
16 changed files with 201 additions and 46 deletions

View file

@ -305,6 +305,7 @@ function modelFromLanguage(info: ModelV2.Info, language: LanguageModelV3) {
const route: AnyRoute = {
id: `ai-sdk:${ProviderV2.packageName(info.package) ?? "unknown"}`,
provider: ProviderID.make(info.providerID),
providerMetadataKey: optionKey,
protocol: "ai-sdk",
endpoint: Endpoint.path("/", { baseURL: "https://ai-sdk.local" }),
auth: Auth.none,
@ -417,7 +418,7 @@ function assistantPart(part: ContentPart): AssistantContent {
case "media":
return [{ type: "file", mediaType: part.mediaType, data: part.data, filename: part.filename }]
case "reasoning":
return [{ type: "reasoning", text: part.text }]
return [{ type: "reasoning", text: part.text, providerOptions: providerOptions(part.providerMetadata) }]
case "tool-call":
return [
{
@ -426,6 +427,7 @@ function assistantPart(part: ContentPart): AssistantContent {
toolName: part.name,
input: part.input,
providerExecuted: part.providerExecuted,
providerOptions: providerOptions(part.providerMetadata),
},
]
case "tool-result":
@ -441,6 +443,7 @@ function toolResultPart(part: ContentPart): ToolResultContent[] {
toolCallId: part.id,
toolName: part.name,
output: toolOutput(part.result),
providerOptions: providerOptions(part.providerMetadata),
},
]
}

View file

@ -233,6 +233,7 @@ const layer = Layer.effect(
}
const resolved = yield* models.resolve(session)
const model = resolved.model
const providerMetadataKey = model.route.providerMetadataKey ?? model.provider
const entries = yield* SessionHistory.entriesForRunner(db, session.id, checkpoint.baselineSeq)
const context = entries.map((entry) => entry.message)
const isLastStep = agent.info?.steps !== undefined && currentStep >= agent.info.steps
@ -250,7 +251,7 @@ const layer = Layer.effect(
.filter((part): part is string => part !== undefined && part.length > 0)
.map(SystemPart.make),
messages: [
...toLLMMessages(context, resolved.ref),
...toLLMMessages(context, resolved.ref, providerMetadataKey),
...(isLastStep ? [Message.assistant(MAX_STEPS_PROMPT)] : []),
],
tools: toolMaterialization?.definitions ?? [],
@ -293,7 +294,7 @@ const layer = Layer.effect(
// The selected catalog identity, not model.id: route-level ids are provider API
// model ids (for example gpt-5.5-fast resolves to api id gpt-5.5).
model: resolved.ref,
provider: model.provider,
providerMetadataKey,
snapshot: startSnapshot,
assistantMessageID,
})

View file

@ -14,7 +14,7 @@ type Input = {
readonly sessionID: SessionSchema.ID
readonly agent: AgentV2.ID
readonly model: ModelV2.Ref
readonly provider: string
readonly providerMetadataKey: string
readonly snapshot?: Snapshot.ID
readonly assistantMessageID?: SessionMessage.ID
}
@ -87,7 +87,9 @@ export const createLLMEventPublisher = (events: EventV2.Interface, input: Input)
assistantMessageID ??= SessionMessage.ID.create()
stepStarted = true
yield* events.publish(SessionEvent.Step.Started, {
...input,
sessionID: input.sessionID,
agent: input.agent,
model: input.model,
assistantMessageID,
snapshot: input.snapshot,
})
@ -97,8 +99,7 @@ export const createLLMEventPublisher = (events: EventV2.Interface, input: Input)
assistantMessageID === undefined
? Effect.die(new Error("Tool event before assistant step start"))
: Effect.succeed(assistantMessageID)
const providerState = (metadata: ProviderMetadata | undefined) => metadata?.[input.provider]
const providerState = (metadata: ProviderMetadata | undefined) => metadata?.[input.providerMetadataKey]
const fragments = (
name: string,
ended: (id: string, value: string, ordinal: number, state?: Record<string, unknown>) => Effect.Effect<void>,
@ -352,14 +353,13 @@ export const createLLMEventPublisher = (events: EventV2.Interface, input: Input)
if (tool.called) return yield* Effect.die(new Error(`Duplicate tool call: ${event.id}`))
tool.called = true
tool.providerExecuted = event.providerExecuted === true
const state = providerState(event.providerMetadata)
yield* events.publish(SessionEvent.Tool.Called, {
sessionID: input.sessionID,
assistantMessageID: tool.assistantMessageID,
callID: event.id,
input: record(event.input),
executed: tool.providerExecuted,
state,
state: providerState(event.providerMetadata),
})
return
}

View file

@ -113,7 +113,7 @@ const toolResult = (tool: SessionMessage.AssistantTool, providerMetadata: Provid
}
}
const assistant = (message: SessionMessage.Assistant, model: ModelV2.Ref) => {
const assistant = (message: SessionMessage.Assistant, model: ModelV2.Ref, providerMetadataKey: string) => {
const sameModel =
String(message.model.providerID) === String(model.providerID) && String(message.model.id) === String(model.id)
const reuseProviderMetadata = sameModel && message.error === undefined
@ -125,7 +125,7 @@ const assistant = (message: SessionMessage.Assistant, model: ModelV2.Ref) => {
{
type: "reasoning",
text: item.text,
providerMetadata: providerMetadata(model.providerID, item.state),
providerMetadata: providerMetadata(providerMetadataKey, item.state),
},
]
: item.text.length > 0
@ -133,13 +133,13 @@ const assistant = (message: SessionMessage.Assistant, model: ModelV2.Ref) => {
: []
const call = toolCall(
item,
reuseProviderMetadata ? providerMetadata(model.providerID, item.providerState) : undefined,
reuseProviderMetadata ? providerMetadata(providerMetadataKey, item.providerState) : undefined,
)
if (item.executed !== true) return [call]
const result = toolResult(
item,
reuseProviderMetadata
? providerMetadata(model.providerID, item.providerResultState ?? item.providerState)
? providerMetadata(providerMetadataKey, item.providerResultState ?? item.providerState)
: undefined,
)
return result ? [call, result] : [call]
@ -155,7 +155,7 @@ const assistant = (message: SessionMessage.Assistant, model: ModelV2.Ref) => {
toolResult(
item,
reuseProviderMetadata
? providerMetadata(model.providerID, item.providerResultState ?? item.providerState)
? providerMetadata(providerMetadataKey, item.providerResultState ?? item.providerState)
: undefined,
),
)
@ -168,7 +168,7 @@ const assistant = (message: SessionMessage.Assistant, model: ModelV2.Ref) => {
]
}
function toLLMMessage(message: SessionMessage.Info, model: ModelV2.Ref): Message[] {
function toLLMMessage(message: SessionMessage.Info, model: ModelV2.Ref, providerMetadataKey: string): Message[] {
switch (message.type) {
case "agent-switched":
case "model-switched":
@ -207,7 +207,7 @@ function toLLMMessage(message: SessionMessage.Info, model: ModelV2.Ref): Message
}),
]
case "assistant":
return assistant(message, model)
return assistant(message, model, providerMetadataKey)
case "compaction":
if (message.status !== "completed") return []
return [
@ -232,5 +232,8 @@ ${message.recent}
}
/** Translate projected V2 Session history into canonical @opencode-ai/llm context. */
export const toLLMMessages = (messages: readonly SessionMessage.Info[], model: ModelV2.Ref) =>
messages.flatMap((message) => toLLMMessage(message, model))
export const toLLMMessages = (
messages: readonly SessionMessage.Info[],
model: ModelV2.Ref,
providerMetadataKey: string = model.providerID,
) => messages.flatMap((message) => toLLMMessage(message, model, providerMetadataKey))