@larkup/workspace-server 0.2.1 → 0.2.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"route.d.ts","sourceRoot":"","sources":["../../../../src/app/api/chat/route.ts"],"names":[],"mappings":"AA+GA,eAAO,MAAM,WAAW,MAAM,CAAC;AAktB/B,wBAAsB,IAAI,CAAC,GAAG,EAAE,OAAO,qBAm6CtC"}
1
+ {"version":3,"file":"route.d.ts","sourceRoot":"","sources":["../../../../src/app/api/chat/route.ts"],"names":[],"mappings":"AAkHA,eAAO,MAAM,WAAW,MAAM,CAAC;AAktB/B,wBAAsB,IAAI,CAAC,GAAG,EAAE,OAAO,qBAu7CtC"}
@@ -1 +1 @@
1
- {"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../../../../src/app/api/chat/tools.ts"],"names":[],"mappings":"AA2XA,wBAAsB,kBAAkB,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,IAAI;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0I7F;AAyVD,wBAAsB,YAAY,CAAC,OAAO,EAAE;IAC1C,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,MAAM,CAAC,EAAE,GAAG,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,iGAAiG;IACjG,wBAAwB,CAAC,EAAE,MAAM,CAAC;IAClC,6EAA6E;IAC7E,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,wEAAwE;IACxE,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B,gGAAgG;IAChG,iBAAiB,CAAC,EAAE;QAClB,OAAO,EAAE,MAAM,EAAE,CAAC;QAClB,IAAI,EAAE,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;KACtC,CAAC;IACF,oFAAoF;IACpF,wBAAwB,CAAC,EAAE;QACzB,OAAO,EAAE,MAAM,EAAE,CAAC;QAClB,IAAI,EAAE,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;KACtC,CAAC;IACF,2FAA2F;IAC3F,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;;;;;;GAumFA"}
1
+ {"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../../../../src/app/api/chat/tools.ts"],"names":[],"mappings":"AA+WA,wBAAsB,kBAAkB,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,IAAI;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0I7F;AAyVD,wBAAsB,YAAY,CAAC,OAAO,EAAE;IAC1C,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,MAAM,CAAC,EAAE,GAAG,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,iGAAiG;IACjG,wBAAwB,CAAC,EAAE,MAAM,CAAC;IAClC,6EAA6E;IAC7E,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,wEAAwE;IACxE,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B,gGAAgG;IAChG,iBAAiB,CAAC,EAAE;QAClB,OAAO,EAAE,MAAM,EAAE,CAAC;QAClB,IAAI,EAAE,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;KACtC,CAAC;IACF,oFAAoF;IACpF,wBAAwB,CAAC,EAAE;QACzB,OAAO,EAAE,MAAM,EAAE,CAAC;QAClB,IAAI,EAAE,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;KACtC,CAAC;IACF,2FAA2F;IAC3F,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;;;;;;GAumFA"}
@@ -10,5 +10,7 @@ export declare function leadingMediaAssetId(retrieval: {
10
10
  }, activeAssetIds: ReadonlySet<string>, assetIdByDocumentId: ReadonlyMap<string, string>): string | undefined;
11
11
  /** Read only an explicitly routed media source, never metadata on secondary search hits. */
12
12
  export declare function explicitMediaEvidenceAssetId(value: unknown): string | null;
13
+ /** A deterministic gate before an answer model is allowed to compose prose. */
14
+ export declare function hasRetrievedSourceEvidence(value: unknown): boolean;
13
15
  export {};
14
16
  //# sourceMappingURL=media-retrieval-routing.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"media-retrieval-routing.d.ts","sourceRoot":"","sources":["../../../src/lib/chat/media-retrieval-routing.ts"],"names":[],"mappings":"AAAA,KAAK,YAAY,GAAG;IAClB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,QAAQ,CAAC,EAAE;QAAE,YAAY,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC;CACvC,CAAC;AAEF,gFAAgF;AAChF,wBAAgB,mBAAmB,CACjC,SAAS,EAAE;IAAE,IAAI,CAAC,EAAE,YAAY,EAAE,CAAA;CAAE,EACpC,cAAc,EAAE,WAAW,CAAC,MAAM,CAAC,EACnC,mBAAmB,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,GAC/C,MAAM,GAAG,SAAS,CASpB;AAED,4FAA4F;AAC5F,wBAAgB,4BAA4B,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAc1E"}
1
+ {"version":3,"file":"media-retrieval-routing.d.ts","sourceRoot":"","sources":["../../../src/lib/chat/media-retrieval-routing.ts"],"names":[],"mappings":"AAAA,KAAK,YAAY,GAAG;IAClB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,QAAQ,CAAC,EAAE;QAAE,YAAY,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC;CACvC,CAAC;AAEF,gFAAgF;AAChF,wBAAgB,mBAAmB,CACjC,SAAS,EAAE;IAAE,IAAI,CAAC,EAAE,YAAY,EAAE,CAAA;CAAE,EACpC,cAAc,EAAE,WAAW,CAAC,MAAM,CAAC,EACnC,mBAAmB,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,GAC/C,MAAM,GAAG,SAAS,CASpB;AAED,4FAA4F;AAC5F,wBAAgB,4BAA4B,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAc1E;AAED,+EAA+E;AAC/E,wBAAgB,0BAA0B,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAOlE"}
@@ -15,6 +15,9 @@ export declare function clearlyTitleMatchedMediaAsset<T extends RoutableMediaAss
15
15
  * retrieval miss, but it must never pre-empt a document or tabular hit.
16
16
  */
17
17
  export declare function explicitlySelectedMediaAsset<T extends RoutableMediaAsset>(query: string, assets: readonly T[]): T | undefined;
18
+ /** Recover media after a miss only when the user named the source clearly.
19
+ * A sole video is not evidence for every unrelated question. */
20
+ export declare function retrievalFallbackMediaAsset<T extends RoutableMediaAsset>(query: string, assets: readonly T[]): T | undefined;
18
21
  /**
19
22
  * Detect an explicitly named source that exists but is outside the Assistant
20
23
  * source scope. A generic single-source fallback must not silently replace it
@@ -1 +1 @@
1
- {"version":3,"file":"media-source-routing.d.ts","sourceRoot":"","sources":["../../../src/lib/chat/media-source-routing.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,kBAAkB,GAAG;IAC/B,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,gBAAgB,EAAE,MAAM,CAAC;IACzB,8BAA8B,CAAC,EAAE,MAAM,CAAC;IACxC,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,EAAE,CAAC;CACvB,CAAC;AAEF,oFAAoF;AACpF,wBAAgB,6BAA6B,CAAC,CAAC,SAAS,kBAAkB,EACxE,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,SAAS,CAAC,EAAE,GACnB,CAAC,GAAG,SAAS,CAUf;AAED;;;;GAIG;AACH,wBAAgB,4BAA4B,CAAC,CAAC,SAAS,kBAAkB,EACvE,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,SAAS,CAAC,EAAE,GACnB,CAAC,GAAG,SAAS,CAEf;AAED;;;;GAIG;AACH,wBAAgB,wCAAwC,CAAC,CAAC,SAAS,kBAAkB,EACnF,KAAK,EAAE,MAAM,EACb,eAAe,EAAE,SAAS,CAAC,EAAE,EAC7B,SAAS,EAAE,SAAS,CAAC,EAAE,GACtB,CAAC,GAAG,SAAS,CAIf;AAED;;;;;;GAMG;AACH,wBAAgB,2BAA2B,CAAC,CAAC,SAAS,kBAAkB,EACtE,MAAM,EAAE,MAAM,EACd,WAAW,EAAE,CAAC,EACd,eAAe,EAAE,SAAS,CAAC,EAAE,WAG9B;AAED,wFAAwF;AACxF,wBAAgB,yBAAyB,CACvC,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,kBAAkB,EACzB,cAAc,CAAC,EAAE,MAAM;;;;;;;;;;;;;;;;;;;EAyBxB"}
1
+ {"version":3,"file":"media-source-routing.d.ts","sourceRoot":"","sources":["../../../src/lib/chat/media-source-routing.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,kBAAkB,GAAG;IAC/B,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,gBAAgB,EAAE,MAAM,CAAC;IACzB,8BAA8B,CAAC,EAAE,MAAM,CAAC;IACxC,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,EAAE,CAAC;CACvB,CAAC;AAEF,oFAAoF;AACpF,wBAAgB,6BAA6B,CAAC,CAAC,SAAS,kBAAkB,EACxE,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,SAAS,CAAC,EAAE,GACnB,CAAC,GAAG,SAAS,CAUf;AAED;;;;GAIG;AACH,wBAAgB,4BAA4B,CAAC,CAAC,SAAS,kBAAkB,EACvE,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,SAAS,CAAC,EAAE,GACnB,CAAC,GAAG,SAAS,CAEf;AAED;gEACgE;AAChE,wBAAgB,2BAA2B,CAAC,CAAC,SAAS,kBAAkB,EACtE,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,SAAS,CAAC,EAAE,GACnB,CAAC,GAAG,SAAS,CAEf;AAED;;;;GAIG;AACH,wBAAgB,wCAAwC,CAAC,CAAC,SAAS,kBAAkB,EACnF,KAAK,EAAE,MAAM,EACb,eAAe,EAAE,SAAS,CAAC,EAAE,EAC7B,SAAS,EAAE,SAAS,CAAC,EAAE,GACtB,CAAC,GAAG,SAAS,CAIf;AAED;;;;;;GAMG;AACH,wBAAgB,2BAA2B,CAAC,CAAC,SAAS,kBAAkB,EACtE,MAAM,EAAE,MAAM,EACd,WAAW,EAAE,CAAC,EACd,eAAe,EAAE,SAAS,CAAC,EAAE,WAG9B;AAED,wFAAwF;AACxF,wBAAgB,yBAAyB,CACvC,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,kBAAkB,EACzB,cAAc,CAAC,EAAE,MAAM;;;;;;;;;;;;;;;;;;;EAyBxB"}
@@ -34,7 +34,7 @@ export interface UrlInspection {
34
34
  isYouTube: boolean;
35
35
  }
36
36
  /** Download a public URL after applying the same DNS and redirect checks as media imports. */
37
- export declare function readPublicUrl(url: string, maxBytes?: number): Promise<{
37
+ export declare function readPublicUrl(url: string, maxBytes?: number, init?: RequestInit): Promise<{
38
38
  bytes: Uint8Array;
39
39
  mimeType?: string;
40
40
  finalUrl: string;
@@ -57,6 +57,21 @@ interface YouTubeInspectionData {
57
57
  duration?: number;
58
58
  }>;
59
59
  }
60
+ interface YouTubePlayerData {
61
+ videoDetails?: {
62
+ videoId?: string;
63
+ title?: string;
64
+ lengthSeconds?: string | number;
65
+ };
66
+ streamingData?: {
67
+ formats?: Array<{
68
+ approxDurationMs?: string | number;
69
+ }>;
70
+ adaptiveFormats?: Array<{
71
+ approxDurationMs?: string | number;
72
+ }>;
73
+ };
74
+ }
60
75
  export declare function inspectYouTubeMetadata(url: string, data: YouTubeInspectionData): UrlInspection;
61
76
  /**
62
77
  * A public watch URL is enough to stage one video even when an interactive
@@ -65,6 +80,8 @@ export declare function inspectYouTubeMetadata(url: string, data: YouTubeInspect
65
80
  * fallback does not claim a title, duration, or byte size it could not verify.
66
81
  */
67
82
  export declare function inspectYouTubeWatchUrl(url: string): UrlInspection | undefined;
83
+ export declare function inspectYouTubeWatchPage(url: string, html: string): UrlInspection | undefined;
84
+ export declare function inspectYouTubePlayerMetadata(url: string, data: YouTubePlayerData): UrlInspection | undefined;
68
85
  export interface MediaProbe {
69
86
  durationSecs: number;
70
87
  containerDurationSecs: number;
@@ -1 +1 @@
1
- {"version":3,"file":"source-utils.d.ts","sourceRoot":"","sources":["../../../src/lib/media/source-utils.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"source-utils.d.ts","sourceRoot":"","sources":["../../../src/lib/media/source-utils.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@larkup/workspace-server",
3
- "version": "0.2.1",
3
+ "version": "0.2.6",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "src",
@@ -324,8 +324,8 @@
324
324
  "zod": "^4.4.3",
325
325
  "mammoth": "^1.12.0",
326
326
  "@larkup/marketplace": "0.3.10",
327
- "@larkup/sandbox": "0.1.8",
328
327
  "@larkup/scraper": "0.2.1",
328
+ "@larkup/sandbox": "0.1.8",
329
329
  "@larkup/vector-stores": "0.1.27"
330
330
  },
331
331
  "devDependencies": {
@@ -99,7 +99,10 @@ import {
99
99
  import { assistantSourceScopeFingerprint } from '../../../lib/chat/assistant-source-scope';
100
100
  import { normalizeIncomingMessages } from '../../../lib/chat/message-input';
101
101
  import { repairChatToolCall } from '../../../lib/chat/tool-call-repair';
102
- import { explicitMediaEvidenceAssetId } from '../../../lib/chat/media-retrieval-routing';
102
+ import {
103
+ explicitMediaEvidenceAssetId,
104
+ hasRetrievedSourceEvidence,
105
+ } from '../../../lib/chat/media-retrieval-routing';
103
106
  import {
104
107
  authorizeEnterpriseAiRequest,
105
108
  trackEnterpriseAiUsage,
@@ -1458,6 +1461,26 @@ ${fieldLines}`;
1458
1461
  output: preloadedEvidence,
1459
1462
  });
1460
1463
 
1464
+ // End no-evidence turns deterministically. A prose model may answer
1465
+ // from general knowledge or mistake a sole media source for evidence
1466
+ // even when the retrieval instruction says not to.
1467
+ if (!hasRetrievedSourceEvidence(preloadedEvidence)) {
1468
+ const retrievalError =
1469
+ preloadedEvidence && typeof preloadedEvidence === 'object'
1470
+ ? (preloadedEvidence as { error?: unknown }).error
1471
+ : undefined;
1472
+ const answer =
1473
+ typeof retrievalError === 'string' && retrievalError.trim()
1474
+ ? retrievalError
1475
+ : "I couldn't find an answer to that in this project's data.";
1476
+ const answerId = `answer-${crypto.randomUUID()}`;
1477
+ writer.write({ type: 'text-start', id: answerId });
1478
+ writer.write({ type: 'text-delta', id: answerId, delta: answer });
1479
+ writer.write({ type: 'text-end', id: answerId });
1480
+ await mcp.close();
1481
+ return;
1482
+ }
1483
+
1461
1484
  // A PDF hit is a document-level locator, not necessarily the exact
1462
1485
  // page. Read its bounded local page evidence before asking the chat
1463
1486
  // model to compose an answer. This works whether or not visual
@@ -88,6 +88,7 @@ import {
88
88
  clearlyTitleMatchedMediaAsset,
89
89
  clearlyUnavailableTitleMatchedMediaAsset,
90
90
  explicitlySelectedMediaAsset,
91
+ retrievalFallbackMediaAsset,
91
92
  shouldKeepActiveMediaSource,
92
93
  } from '../../../lib/chat/media-source-routing';
93
94
  import { createTabularVisualization } from '../../../lib/chat/tabular-visualization';
@@ -348,24 +349,11 @@ function uniqueCompletedMediaFallback(
348
349
  error: `The named media source "${unavailableTitleMatch.fileName}" is not available to Assistant chat. Enable its data group under Data → Groups, then ask again.`,
349
350
  };
350
351
  }
351
- // A retrieval miss must not strand a single, unambiguous active media
352
- // source. With several sources the host deliberately stays neutral and
353
- // lets ordinary retrieval disambiguate rather than guessing the asset.
354
- if (assets.length === 1) {
355
- return {
356
- query,
357
- hits: [],
358
- videoEvidence: {
359
- mediaAssetId: assets[0].id,
360
- retrievalFallback: 'single-active-media-source',
361
- },
362
- };
363
- }
364
352
  // A vector index can be temporarily unavailable while a completed video
365
353
  // remains perfectly usable through its evidence store. Recover an asset
366
- // only when its user-visible title has a clear lexical lead; otherwise
367
- // preserve the neutral no-result behavior for genuinely ambiguous media.
368
- const titleMatch = clearlyTitleMatchedMediaAsset(query, assets);
354
+ // only when its user-visible title has a clear lexical lead. The mere fact
355
+ // that a project contains one video is not evidence for an unrelated query.
356
+ const titleMatch = retrievalFallbackMediaAsset(query, assets);
369
357
  if (!titleMatch) return { query, hits: [] };
370
358
  return {
371
359
  query,
@@ -35,3 +35,13 @@ export function explicitMediaEvidenceAssetId(value: unknown): string | null {
35
35
  }
36
36
  return explicitMediaEvidenceAssetId(record.videoEvidence);
37
37
  }
38
+
39
+ /** A deterministic gate before an answer model is allowed to compose prose. */
40
+ export function hasRetrievedSourceEvidence(value: unknown): boolean {
41
+ if (!value || typeof value !== 'object' || Array.isArray(value)) return false;
42
+ const record = value as { hits?: unknown };
43
+ return (
44
+ (Array.isArray(record.hits) && record.hits.length > 0) ||
45
+ Boolean(explicitMediaEvidenceAssetId(value))
46
+ );
47
+ }
@@ -38,6 +38,15 @@ export function explicitlySelectedMediaAsset<T extends RoutableMediaAsset>(
38
38
  return clearlyTitleMatchedMediaAsset(query, assets);
39
39
  }
40
40
 
41
+ /** Recover media after a miss only when the user named the source clearly.
42
+ * A sole video is not evidence for every unrelated question. */
43
+ export function retrievalFallbackMediaAsset<T extends RoutableMediaAsset>(
44
+ query: string,
45
+ assets: readonly T[],
46
+ ): T | undefined {
47
+ return clearlyTitleMatchedMediaAsset(query, assets);
48
+ }
49
+
41
50
  /**
42
51
  * Detect an explicitly named source that exists but is outside the Assistant
43
52
  * source scope. A generic single-source fallback must not silently replace it
@@ -43,8 +43,9 @@ export interface UrlInspection {
43
43
  export async function readPublicUrl(
44
44
  url: string,
45
45
  maxBytes = 25 * 1024 * 1024,
46
+ init: RequestInit = {},
46
47
  ): Promise<{ bytes: Uint8Array; mimeType?: string; finalUrl: string }> {
47
- const response = await fetchPublic(url);
48
+ const response = await fetchPublic(url, init);
48
49
  if (!response.ok || !response.body) {
49
50
  await response.body?.cancel();
50
51
  throw new Error(`Media download failed (${response.status}).`);
@@ -98,6 +99,18 @@ interface YouTubeInspectionData {
98
99
  }>;
99
100
  }
100
101
 
102
+ interface YouTubePlayerData {
103
+ videoDetails?: {
104
+ videoId?: string;
105
+ title?: string;
106
+ lengthSeconds?: string | number;
107
+ };
108
+ streamingData?: {
109
+ formats?: Array<{ approxDurationMs?: string | number }>;
110
+ adaptiveFormats?: Array<{ approxDurationMs?: string | number }>;
111
+ };
112
+ }
113
+
101
114
  function positiveDuration(value: unknown): number | undefined {
102
115
  const duration = Number(value);
103
116
  return Number.isFinite(duration) && duration > 0 ? duration : undefined;
@@ -170,6 +183,107 @@ export function inspectYouTubeWatchUrl(url: string): UrlInspection | undefined {
170
183
  };
171
184
  }
172
185
 
186
+ export function inspectYouTubeWatchPage(url: string, html: string): UrlInspection | undefined {
187
+ const details = jsonObjectAfterMarker(html, '"videoDetails":');
188
+ if (details) {
189
+ try {
190
+ const inspection = inspectYouTubePlayerMetadata(url, {
191
+ videoDetails: JSON.parse(details) as YouTubePlayerData['videoDetails'],
192
+ });
193
+ if (inspection) return inspection;
194
+ } catch {
195
+ // Continue to the page's public schema.org duration metadata.
196
+ }
197
+ }
198
+ const requestedId = youtubeVideoId(url);
199
+ const durationSecs = youtubePageDurationSecs(html);
200
+ return requestedId && durationSecs
201
+ ? youtubeInspection(url, requestedId, undefined, durationSecs)
202
+ : undefined;
203
+ }
204
+
205
+ export function inspectYouTubePlayerMetadata(
206
+ url: string,
207
+ data: YouTubePlayerData,
208
+ ): UrlInspection | undefined {
209
+ const requestedId = youtubeVideoId(url);
210
+ const details = data.videoDetails;
211
+ const declaredDurationSecs = positiveDuration(details?.lengthSeconds);
212
+ if (requestedId && declaredDurationSecs && details?.videoId === requestedId) {
213
+ return youtubeInspection(url, requestedId, details.title, declaredDurationSecs);
214
+ }
215
+ // Some serverless requests receive a reduced but still valid player payload:
216
+ // `videoDetails` is omitted while the signed selected-stream metadata remains.
217
+ // It is tied to the requested ID in this player request, so use a rounded
218
+ // positive stream duration only when the response does not claim another ID.
219
+ const streamDurationSecs = playerStreamDurationSecs(data);
220
+ if (!requestedId || data.videoDetails?.videoId || !streamDurationSecs) return undefined;
221
+ return youtubeInspection(url, requestedId, data.videoDetails?.title, streamDurationSecs);
222
+ }
223
+
224
+ function youtubeInspection(
225
+ url: string,
226
+ requestedId: string,
227
+ title: string | undefined,
228
+ durationSecs: number,
229
+ ): UrlInspection {
230
+ return {
231
+ originalUrl: url,
232
+ ...(title ? { title } : {}),
233
+ durationSecs,
234
+ singleItemDurationSecs: durationSecs,
235
+ singleItemUrl: `https://www.youtube.com/watch?v=${encodeURIComponent(requestedId)}`,
236
+ entryCount: 1,
237
+ mediaType: 'video',
238
+ isYouTube: true,
239
+ };
240
+ }
241
+
242
+ function playerStreamDurationSecs(data: YouTubePlayerData): number | undefined {
243
+ const durations = [
244
+ ...(data.streamingData?.formats ?? []),
245
+ ...(data.streamingData?.adaptiveFormats ?? []),
246
+ ]
247
+ .map((format) => positiveDuration(Number(format.approxDurationMs) / 1_000))
248
+ .filter((duration): duration is number => duration !== undefined);
249
+ return durations.length ? Math.round(Math.max(...durations)) : undefined;
250
+ }
251
+
252
+ function youtubePageDurationSecs(html: string): number | undefined {
253
+ const durationTag = html.match(
254
+ /<meta\b(?=[^>]*\bitemprop=["']duration["'])(?=[^>]*\bcontent=["']([^"']+)["'])[^>]*>/i,
255
+ );
256
+ const durationText = durationTag?.[1];
257
+ if (!durationText) return undefined;
258
+ const match = durationText.match(/^PT(?:(\d+)H)?(?:(\d+)M)?(?:(\d+)S)?$/i);
259
+ if (!match) return undefined;
260
+ return positiveDuration(
261
+ Number(match[1] ?? 0) * 3_600 + Number(match[2] ?? 0) * 60 + Number(match[3] ?? 0),
262
+ );
263
+ }
264
+
265
+ function jsonObjectAfterMarker(source: string, marker: string): string | undefined {
266
+ const markerIndex = source.indexOf(marker);
267
+ const start = markerIndex < 0 ? -1 : source.indexOf('{', markerIndex + marker.length);
268
+ if (start < 0) return undefined;
269
+ let depth = 0;
270
+ let inString = false;
271
+ let escaped = false;
272
+ for (let index = start; index < source.length; index++) {
273
+ const character = source[index];
274
+ if (inString) {
275
+ if (escaped) escaped = false;
276
+ else if (character === '\\') escaped = true;
277
+ else if (character === '"') inString = false;
278
+ continue;
279
+ }
280
+ if (character === '"') inString = true;
281
+ else if (character === '{') depth += 1;
282
+ else if (character === '}' && --depth === 0) return source.slice(start, index + 1);
283
+ }
284
+ return undefined;
285
+ }
286
+
173
287
  export interface MediaProbe {
174
288
  durationSecs: number;
175
289
  containerDurationSecs: number;
@@ -313,6 +427,48 @@ export function hasInflatedPlaybackTimeline(probe: MediaProbe) {
313
427
  return longestReported - videoDuration > Math.max(5, videoDuration * 0.03);
314
428
  }
315
429
 
430
+ async function inspectYouTubePlayer(url: string): Promise<UrlInspection | undefined> {
431
+ const videoId = youtubeVideoId(url);
432
+ if (!videoId) return undefined;
433
+ const response = await fetchPublic(
434
+ 'https://www.youtube.com/youtubei/v1/player?prettyPrint=false',
435
+ {
436
+ method: 'POST',
437
+ headers: {
438
+ 'content-type': 'application/json',
439
+ 'user-agent': 'com.google.android.youtube/20.10.38 (Linux; U; Android 11) gzip',
440
+ },
441
+ body: JSON.stringify({
442
+ context: {
443
+ client: {
444
+ clientName: 'ANDROID',
445
+ clientVersion: '20.10.38',
446
+ androidSdkVersion: 30,
447
+ hl: 'en',
448
+ gl: 'US',
449
+ },
450
+ },
451
+ videoId,
452
+ contentCheckOk: true,
453
+ racyCheckOk: true,
454
+ }),
455
+ signal: AbortSignal.timeout(MEDIA_URL_INSPECTION_TIMEOUT_MS),
456
+ },
457
+ );
458
+ if (!response.ok) {
459
+ await response.body?.cancel();
460
+ return undefined;
461
+ }
462
+ const declared = Number(response.headers.get('content-length'));
463
+ if (Number.isFinite(declared) && declared > 3 * 1024 * 1024) {
464
+ await response.body?.cancel();
465
+ return undefined;
466
+ }
467
+ const body = await response.text();
468
+ if (body.length > 3 * 1024 * 1024) return undefined;
469
+ return inspectYouTubePlayerMetadata(url, JSON.parse(body) as YouTubePlayerData);
470
+ }
471
+
316
472
  export async function inspectMediaUrl(url: string): Promise<UrlInspection> {
317
473
  const parsed = validHttpUrl(url);
318
474
  await assertPublicHost(parsed.hostname);
@@ -321,6 +477,21 @@ export async function inspectMediaUrl(url: string): Promise<UrlInspection> {
321
477
  try {
322
478
  output = await runYtDlp(['--dump-single-json', '--simulate', '--flat-playlist', url]);
323
479
  } catch (error) {
480
+ const playerInspection = await inspectYouTubePlayer(url).catch(() => undefined);
481
+ if (playerInspection) return playerInspection;
482
+ try {
483
+ const page = await readPublicUrl(url, 3 * 1024 * 1024, {
484
+ headers: {
485
+ // YouTube otherwise serves a reduced challenge page to serverless
486
+ // fetch clients, which excludes its public schema.org duration.
487
+ 'user-agent': 'Mozilla/5.0 (compatible; Larkup/1.0; +https://larkup.de)',
488
+ },
489
+ });
490
+ const inspection = inspectYouTubeWatchPage(url, new TextDecoder().decode(page.bytes));
491
+ if (inspection) return inspection;
492
+ } catch {
493
+ // A public watch URL can still be staged for durable inspection below.
494
+ }
324
495
  const fallback = inspectYouTubeWatchUrl(url);
325
496
  if (fallback) return fallback;
326
497
  throw error;