2026-05-15 16:26:57 +09:00
|
|
|
import { getServerBasicAuthHeader as resolveServerBasicAuthHeader } from "./opencode-server-auth"
|
|
|
|
|
import { log as writeLog } from "./logger"
|
2026-02-16 15:08:10 +09:00
|
|
|
import { isRecord } from "./record-type-guard"
|
2026-02-15 14:48:14 +09:00
|
|
|
|
|
|
|
|
type UnknownRecord = Record<string, unknown>
|
2026-05-15 16:26:57 +09:00
|
|
|
type FetchImplementation = typeof fetch
|
|
|
|
|
type LogImplementation = typeof writeLog
|
|
|
|
|
type ServerBasicAuthHeaderResolver = typeof resolveServerBasicAuthHeader
|
|
|
|
|
|
|
|
|
|
let fetchImplementationForTesting: FetchImplementation | undefined
|
|
|
|
|
let logImplementationForTesting: LogImplementation | undefined
|
|
|
|
|
let serverBasicAuthHeaderResolverForTesting: ServerBasicAuthHeaderResolver | undefined
|
|
|
|
|
|
|
|
|
|
function getFetchImplementation(): FetchImplementation {
|
|
|
|
|
return fetchImplementationForTesting ?? fetch
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function getLogImplementation(): LogImplementation {
|
|
|
|
|
return logImplementationForTesting ?? writeLog
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function getServerBasicAuthHeaderImplementation(): ServerBasicAuthHeaderResolver {
|
|
|
|
|
return serverBasicAuthHeaderResolverForTesting ?? resolveServerBasicAuthHeader
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function _setFetchImplementationForTesting(fetchImplementation: FetchImplementation | undefined): void {
|
|
|
|
|
fetchImplementationForTesting = fetchImplementation
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function _setLogImplementationForTesting(logImplementation: LogImplementation | undefined): void {
|
|
|
|
|
logImplementationForTesting = logImplementation
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function _setServerBasicAuthHeaderResolverForTesting(
|
|
|
|
|
resolver: ServerBasicAuthHeaderResolver | undefined,
|
|
|
|
|
): void {
|
|
|
|
|
serverBasicAuthHeaderResolverForTesting = resolver
|
|
|
|
|
}
|
2026-02-15 14:48:14 +09:00
|
|
|
|
|
|
|
|
function getInternalClient(client: unknown): UnknownRecord | null {
|
|
|
|
|
if (!isRecord(client)) {
|
|
|
|
|
return null
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const internal = client["_client"]
|
|
|
|
|
return isRecord(internal) ? internal : null
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function getServerBaseUrl(client: unknown): string | null {
|
|
|
|
|
// Try client._client.getConfig().baseUrl
|
|
|
|
|
const internal = getInternalClient(client)
|
|
|
|
|
if (internal) {
|
|
|
|
|
const getConfig = internal["getConfig"]
|
|
|
|
|
if (typeof getConfig === "function") {
|
|
|
|
|
const config = getConfig()
|
|
|
|
|
if (isRecord(config)) {
|
|
|
|
|
const baseUrl = config["baseUrl"]
|
|
|
|
|
if (typeof baseUrl === "string") {
|
|
|
|
|
return baseUrl
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Try client.session._client.getConfig().baseUrl
|
|
|
|
|
if (isRecord(client)) {
|
|
|
|
|
const session = client["session"]
|
|
|
|
|
if (isRecord(session)) {
|
|
|
|
|
const internal = session["_client"]
|
|
|
|
|
if (isRecord(internal)) {
|
|
|
|
|
const getConfig = internal["getConfig"]
|
|
|
|
|
if (typeof getConfig === "function") {
|
|
|
|
|
const config = getConfig()
|
|
|
|
|
if (isRecord(config)) {
|
|
|
|
|
const baseUrl = config["baseUrl"]
|
|
|
|
|
if (typeof baseUrl === "string") {
|
|
|
|
|
return baseUrl
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return null
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export async function patchPart(
|
|
|
|
|
client: unknown,
|
|
|
|
|
sessionID: string,
|
|
|
|
|
messageID: string,
|
|
|
|
|
partID: string,
|
|
|
|
|
body: Record<string, unknown>
|
|
|
|
|
): Promise<boolean> {
|
|
|
|
|
const baseUrl = getServerBaseUrl(client)
|
|
|
|
|
if (!baseUrl) {
|
2026-05-15 16:26:57 +09:00
|
|
|
getLogImplementation()("[opencode-http-api] Could not extract baseUrl from client")
|
2026-02-15 14:48:14 +09:00
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-15 16:26:57 +09:00
|
|
|
const auth = getServerBasicAuthHeaderImplementation()()
|
2026-02-15 14:48:14 +09:00
|
|
|
if (!auth) {
|
2026-05-15 16:26:57 +09:00
|
|
|
getLogImplementation()("[opencode-http-api] No auth header available")
|
2026-02-15 14:48:14 +09:00
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-16 00:28:15 +09:00
|
|
|
const url = `${baseUrl}/session/${encodeURIComponent(sessionID)}/message/${encodeURIComponent(messageID)}/part/${encodeURIComponent(partID)}`
|
2026-02-15 14:48:14 +09:00
|
|
|
|
|
|
|
|
try {
|
2026-05-15 16:26:57 +09:00
|
|
|
const response = await getFetchImplementation()(url, {
|
2026-02-15 14:48:14 +09:00
|
|
|
method: "PATCH",
|
|
|
|
|
headers: {
|
|
|
|
|
"Content-Type": "application/json",
|
|
|
|
|
"Authorization": auth,
|
|
|
|
|
},
|
|
|
|
|
body: JSON.stringify(body),
|
2026-02-16 16:52:23 +09:00
|
|
|
signal: AbortSignal.timeout(10_000),
|
2026-02-15 14:48:14 +09:00
|
|
|
})
|
|
|
|
|
|
|
|
|
|
if (!response.ok) {
|
2026-05-15 16:26:57 +09:00
|
|
|
getLogImplementation()("[opencode-http-api] PATCH failed", { status: response.status, url })
|
2026-02-15 14:48:14 +09:00
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true
|
|
|
|
|
} catch (error) {
|
|
|
|
|
const message = error instanceof Error ? error.message : String(error)
|
2026-05-15 16:26:57 +09:00
|
|
|
getLogImplementation()("[opencode-http-api] PATCH error", { message, url })
|
2026-02-15 14:48:14 +09:00
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export async function deletePart(
|
|
|
|
|
client: unknown,
|
|
|
|
|
sessionID: string,
|
|
|
|
|
messageID: string,
|
|
|
|
|
partID: string
|
|
|
|
|
): Promise<boolean> {
|
|
|
|
|
const baseUrl = getServerBaseUrl(client)
|
|
|
|
|
if (!baseUrl) {
|
2026-05-15 16:26:57 +09:00
|
|
|
getLogImplementation()("[opencode-http-api] Could not extract baseUrl from client")
|
2026-02-15 14:48:14 +09:00
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-15 16:26:57 +09:00
|
|
|
const auth = getServerBasicAuthHeaderImplementation()()
|
2026-02-15 14:48:14 +09:00
|
|
|
if (!auth) {
|
2026-05-15 16:26:57 +09:00
|
|
|
getLogImplementation()("[opencode-http-api] No auth header available")
|
2026-02-15 14:48:14 +09:00
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-16 00:28:15 +09:00
|
|
|
const url = `${baseUrl}/session/${encodeURIComponent(sessionID)}/message/${encodeURIComponent(messageID)}/part/${encodeURIComponent(partID)}`
|
2026-02-15 14:48:14 +09:00
|
|
|
|
|
|
|
|
try {
|
2026-05-15 16:26:57 +09:00
|
|
|
const response = await getFetchImplementation()(url, {
|
2026-02-15 14:48:14 +09:00
|
|
|
method: "DELETE",
|
|
|
|
|
headers: {
|
|
|
|
|
"Authorization": auth,
|
|
|
|
|
},
|
2026-02-16 16:52:23 +09:00
|
|
|
signal: AbortSignal.timeout(10_000),
|
2026-02-15 14:48:14 +09:00
|
|
|
})
|
|
|
|
|
|
|
|
|
|
if (!response.ok) {
|
2026-05-15 16:26:57 +09:00
|
|
|
getLogImplementation()("[opencode-http-api] DELETE failed", { status: response.status, url })
|
2026-02-15 14:48:14 +09:00
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true
|
|
|
|
|
} catch (error) {
|
|
|
|
|
const message = error instanceof Error ? error.message : String(error)
|
2026-05-15 16:26:57 +09:00
|
|
|
getLogImplementation()("[opencode-http-api] DELETE error", { message, url })
|
2026-02-15 14:48:14 +09:00
|
|
|
return false
|
|
|
|
|
}
|
2026-05-15 16:26:57 +09:00
|
|
|
}
|