fix(server): transcript key & bump models (#15485)

#### PR Dependency Tree


* **PR #15485** 👈

This tree was auto-generated by
[Charcoal](https://github.com/danerwilliams/charcoal)

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **New Features**
* Upgraded built-in AI actions and chat model selection to Gemini 3.7
Flash.
* Transcript attachments now retain storage keys and support secure
presigned URLs during processing.
* Completed transcript results preserve attachment metadata for reliable
access.

* **Bug Fixes**
* Improved transcript handling across uploads, retries, and native
processing.
  * Added safeguards for invalid or missing attachment URLs.

* **Maintenance**
* Added a migration to backfill storage keys for existing transcript
records without altering their URLs.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
DarkSky
2026-08-16 00:49:27 +08:00
committed by GitHub
parent 7b3a8afc87
commit ff1e3d9c94
14 changed files with 286 additions and 67 deletions
@@ -59,14 +59,31 @@ export class CopilotStorage {
): Promise<StorageRuntimeGetObjectResult> {
const name = `${userId}/${workspaceId}/${key}`;
if (signedUrl) {
const presigned = await this.rt.presignGet('copilot', name);
if (presigned) {
return { redirectUrl: presigned.url };
}
const redirectUrl = await this.presignGet(userId, workspaceId, key);
if (redirectUrl) return { redirectUrl };
}
return this.rt.getObject('copilot', name);
}
async presignGet(userId: string, workspaceId: string, key: string) {
return (
await this.rt.presignGet('copilot', `${userId}/${workspaceId}/${key}`)
)?.url;
}
keyFromUrl(userId: string, workspaceId: string, url: string) {
try {
const parsed = new URL(url);
const prefix = `/api/copilot/blob/${encodeURIComponent(userId)}/${encodeURIComponent(workspaceId)}/`;
if (parsed.pathname.startsWith(prefix)) {
return decodeURIComponent(parsed.pathname.slice(prefix.length));
}
} catch {
return;
}
return undefined;
}
@CallMetric('ai', 'blob_delete')
async delete(userId: string, workspaceId: string, key: string) {
await this.rt.deleteObject('copilot', `${userId}/${workspaceId}/${key}`);