Files
zeekay 40d120dbf7 refactor(api): say /v1/ in the Python callers
Every first-party Python caller and test now names the mount the API actually
answers on. Mechanical: URL literals only, and only where the /api/ was ours --
a preceding word character means it was a module path like insights/api/ or a
third-party host like slack.com/api/, and those are untouched.

Left saying /api/, each for a reason rather than for safety:
- products/warehouse_sources/.../sources/** is a catalogue of vendor endpoints
- services/llm-gateway deploys separately and is under a code freeze, so moving
  its two calls would break it during the skew
- the mcp_store and Slack/Teams redirect_uris are registered with third parties
  and must byte-match what was registered
- the Wizard and Raycast client-metadata paths are OAuth client_ids
- /api/surveys and /api/feature_flag/local_evaluation are SDK aliases the
  ingress routes to the Rust fleets, held by released SDK versions

Test URLconfs moved with the requests that call them, and the Datastore
user-selection fixtures now carry the leading slash that request.path has.
2026-08-08 18:38:44 -07:00

51 lines
1.9 KiB
TypeScript

/** Frontend twin of the ingestion pointer module: the only place phaiblob:// URIs are interpreted. */
export interface AiBlobPointer {
version: string
algo: string
hash: string
mime: string | null
size: number | null
}
const POINTER_SCHEME = 'phaiblob://'
const POINTER_RE = /^phaiblob:\/\/(v1)\/(sha256)\/([0-9a-f]{64})(?:\?(.*))?$/
// TODO: replace the whole URL rather than its data component, so a valid data URL can't be clobbered.
const DATA_URI_WRAPPED_POINTER = /^data:[\w.+-]+\/[\w.+-]+;base64,(phaiblob:\/\/.*)$/
export function parseAiBlobPointer(value: string): AiBlobPointer | null {
const unwrapped = DATA_URI_WRAPPED_POINTER.exec(value)?.[1] ?? value
if (!unwrapped.startsWith(POINTER_SCHEME)) {
return null
}
const match = POINTER_RE.exec(unwrapped)
if (!match) {
return null
}
const params = new URLSearchParams(match[4] ?? '')
const sizeRaw = params.get('size')
return {
version: match[1],
algo: match[2],
hash: match[3],
mime: params.get('mime'),
size: sizeRaw && /^\d+$/.test(sizeRaw) ? parseInt(sizeRaw, 10) : null,
}
}
export function resolveAiBlobUrl(value: string, teamId: number | string | null): string {
const pointer = parseAiBlobPointer(value)
if (!pointer || teamId === null || teamId === undefined || teamId === '') {
return value
}
return `/v1/projects/${teamId}/ai_blob/${pointer.version}/${pointer.algo}/${pointer.hash}`
}
/**
* Resolves a raw `data` field (base64 payload or, post-offload, a phaiblob:// pointer) to a
* renderable src: the blob endpoint URL if it's a pointer, otherwise the `data:` URI it always was.
*/
export function resolveDataUri(rawData: string, mimeType: string, teamId: number | string | null): string {
const resolved = resolveAiBlobUrl(rawData, teamId)
return resolved !== rawData ? resolved : `data:${mimeType};base64,${rawData}`
}