merge main (0.6.0) into fix/omp-developer-messages
Align the developer-message fix with the Provider API transport router. The fix stays scoped to the legacy /alpha/generate converter, and the OMP advisory compat test now forces the upgrade_required fallback so the advisory is asserted on the generate request body.
This commit is contained in:
+72
-16
@@ -66,9 +66,8 @@ function imageContentError(role: string): Error {
|
||||
|
||||
export function assertTextOnlyMessages(messages?: readonly MessageLike[]): void {
|
||||
for (const message of messages ?? []) {
|
||||
if (imageParts(message.content).length > 0) {
|
||||
const role = message.role === "toolResult" ? "tool results" : `${message.role} messages`
|
||||
throw imageContentError(role)
|
||||
if (message.role !== "toolResult" && imageParts(message.content).length > 0) {
|
||||
throw imageContentError(`${message.role} messages`)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -107,6 +106,7 @@ export function getApiKey(
|
||||
} = {},
|
||||
): string | undefined {
|
||||
const env = options.env ?? process.env
|
||||
if (env.COMMAND_CODE_API_KEY) return env.COMMAND_CODE_API_KEY
|
||||
if (env.COMMANDCODE_API_KEY) return env.COMMANDCODE_API_KEY
|
||||
|
||||
const home = options.homeDir?.() ?? homedir()
|
||||
@@ -138,7 +138,41 @@ export function getApiKey(
|
||||
return undefined
|
||||
}
|
||||
|
||||
// Hosts such as OMP may pass a literal env-var name as the "resolved" registry
|
||||
// key instead of the actual credential. Treat those as unresolved.
|
||||
export const COMMAND_CODE_PLACEHOLDER_KEYS = new Set([
|
||||
"$COMMAND_CODE_API_KEY",
|
||||
"COMMAND_CODE_API_KEY",
|
||||
"$COMMANDCODE_API_KEY",
|
||||
"COMMANDCODE_API_KEY",
|
||||
])
|
||||
|
||||
/**
|
||||
* Pick the real API key from a host registry value and/or the env/auth-file
|
||||
* fallback, never returning a literal placeholder or an empty/whitespace value.
|
||||
* Pure/testable.
|
||||
*/
|
||||
export function pickCommandCodeApiKey(
|
||||
registryKey: string | undefined,
|
||||
hostKey: string | undefined,
|
||||
): string | undefined {
|
||||
const trimmed = typeof registryKey === "string" ? registryKey.trim() : undefined
|
||||
if (!trimmed) return hostKey
|
||||
if (COMMAND_CODE_PLACEHOLDER_KEYS.has(trimmed)) return hostKey
|
||||
return trimmed
|
||||
}
|
||||
|
||||
export function textContent(message: { content?: unknown }): string {
|
||||
if (typeof message.content === "string") return message.content
|
||||
if (message.content === null || message.content === undefined) return ""
|
||||
if (!Array.isArray(message.content)) {
|
||||
try {
|
||||
return JSON.stringify(message.content) ?? String(message.content)
|
||||
} catch {
|
||||
return String(message.content)
|
||||
}
|
||||
}
|
||||
|
||||
return recordArray(message.content)
|
||||
.filter((part) => part.type === "text")
|
||||
.map((part) => stringValue(part.text) ?? "")
|
||||
@@ -159,7 +193,12 @@ export function toolsToJson(tools?: readonly ToolLike[]): unknown[] {
|
||||
}))
|
||||
}
|
||||
|
||||
function completeToolCallIds(messages?: readonly MessageLike[]): Set<string> {
|
||||
interface ToolCallState {
|
||||
callIds: ReadonlySet<string>
|
||||
resultIds: ReadonlySet<string>
|
||||
}
|
||||
|
||||
function toolCallState(messages?: readonly MessageLike[]): ToolCallState {
|
||||
const callIds = new Set<string>()
|
||||
const resultIds = new Set<string>()
|
||||
|
||||
@@ -171,12 +210,12 @@ function completeToolCallIds(messages?: readonly MessageLike[]): Set<string> {
|
||||
if (id) callIds.add(id)
|
||||
}
|
||||
}
|
||||
} else if (message.role === "toolResult") {
|
||||
if (message.toolCallId) resultIds.add(message.toolCallId)
|
||||
} else if (message.role === "toolResult" && message.toolCallId) {
|
||||
resultIds.add(message.toolCallId)
|
||||
}
|
||||
}
|
||||
|
||||
return new Set([...callIds].filter((id) => resultIds.has(id)))
|
||||
return { callIds, resultIds }
|
||||
}
|
||||
|
||||
export function messagesToCC(
|
||||
@@ -187,7 +226,7 @@ export function messagesToCC(
|
||||
if (!allowImages) assertTextOnlyMessages(messages)
|
||||
|
||||
const out: unknown[] = []
|
||||
const pairedToolCallIds = completeToolCallIds(messages)
|
||||
const { callIds, resultIds } = toolCallState(messages)
|
||||
|
||||
for (const message of messages ?? []) {
|
||||
if (message.role === "user" || message.role === "developer") {
|
||||
@@ -202,23 +241,42 @@ export function messagesToCC(
|
||||
})
|
||||
} else if (message.role === "assistant") {
|
||||
const parts: unknown[] = []
|
||||
const missingResults: unknown[] = []
|
||||
for (const content of recordArray(message.content)) {
|
||||
if (content.type === "text") {
|
||||
parts.push({ type: "text", text: stringValue(content.text) ?? "" })
|
||||
} else if (content.type === "toolCall") {
|
||||
const toolCallId = stringValue(content.id) ?? ""
|
||||
if (!pairedToolCallIds.has(toolCallId)) continue
|
||||
const toolName = stringValue(content.name) ?? ""
|
||||
if (!toolCallId) continue
|
||||
parts.push({
|
||||
type: "tool-call",
|
||||
toolCallId,
|
||||
toolName: stringValue(content.name) ?? "",
|
||||
toolName,
|
||||
input: recordOrEmpty(content.arguments),
|
||||
})
|
||||
if (!resultIds.has(toolCallId)) {
|
||||
missingResults.push({
|
||||
type: "tool-result",
|
||||
toolCallId,
|
||||
toolName,
|
||||
output: {
|
||||
type: "error-text",
|
||||
value: "No result — the tool call did not complete (interrupted or lost).",
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
if (parts.length > 0) out.push({ role: "assistant", content: parts })
|
||||
if (missingResults.length > 0) out.push({ role: "tool", content: missingResults })
|
||||
} else if (message.role === "toolResult") {
|
||||
if (!message.toolCallId || !pairedToolCallIds.has(message.toolCallId)) continue
|
||||
if (!message.toolCallId || !callIds.has(message.toolCallId)) continue
|
||||
const images = imageParts(message.content)
|
||||
const text = textContent(message)
|
||||
const outputText =
|
||||
text ||
|
||||
(images.length > 0 && !allowImages ? "[Image omitted: model does not support images]" : "")
|
||||
out.push({
|
||||
role: "tool",
|
||||
content: [
|
||||
@@ -227,15 +285,13 @@ export function messagesToCC(
|
||||
toolCallId: message.toolCallId,
|
||||
toolName: message.toolName,
|
||||
output: message.isError
|
||||
? { type: "error-text", value: textContent(message) }
|
||||
: { type: "text", value: textContent(message) },
|
||||
? { type: "error-text", value: outputText }
|
||||
: { type: "text", value: outputText },
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
const images = imageParts(message.content)
|
||||
if (images.length > 0) {
|
||||
if (!allowImages) throw imageContentError("tool results")
|
||||
if (images.length > 0 && allowImages) {
|
||||
out.push({
|
||||
role: "user",
|
||||
content: images.map(imageToCommandCode),
|
||||
|
||||
Reference in New Issue
Block a user