-
Notifications
You must be signed in to change notification settings - Fork 3.7k
Expand file tree
/
Copy pathutils.ts
More file actions
134 lines (125 loc) · 4.42 KB
/
Copy pathutils.ts
File metadata and controls
134 lines (125 loc) · 4.42 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
/**
* Resolve the document ID for a Docs API request, preferring the selected
* document ID and falling back to a manually entered one. Throws when neither
* is present so callers fail loudly before issuing a request.
*/
export function resolveDocumentId(params: {
documentId?: string
manualDocumentId?: string
}): string {
const documentId = params.documentId?.trim() || params.manualDocumentId?.trim()
if (!documentId) {
throw new Error('Document ID is required')
}
return documentId
}
/**
* Normalize an optional boolean param that may arrive as a real boolean or as a
* string (`"true"`/`"false"`), which happens when values come from block inputs
* or agent tool-calls rather than a typed UI switch. Returns `undefined` when the
* value is absent or unrecognized so callers can skip the field entirely.
*/
export function parseOptionalBoolean(value: unknown): boolean | undefined {
if (typeof value === 'boolean') return value
if (typeof value === 'string') {
const normalized = value.trim().toLowerCase()
if (normalized === 'true') return true
if (normalized === 'false') return false
}
return undefined
}
/**
* Build a `Location` for a batchUpdate request when an insertion index is
* provided, otherwise fall back to `endOfSegmentLocation` (end of the body).
* The Docs API treats index 0 as invalid, so a positive index is required to
* use an explicit location.
*/
export function buildInsertLocation(
index?: number
): { location: { index: number } } | { endOfSegmentLocation: Record<string, never> } {
if (typeof index === 'number' && Number.isFinite(index) && index >= 1) {
return { location: { index } }
}
return { endOfSegmentLocation: {} }
}
/**
* Build and validate a Docs API `Range` from a start and end character index.
* The Docs API requires `endIndex` to be strictly greater than `startIndex`.
* Throws when either index is missing or the range is empty/inverted so callers
* fail loudly before issuing a request.
*/
export function buildContentRange(
startIndex: unknown,
endIndex: unknown
): { startIndex: number; endIndex: number } {
const start = Number(startIndex)
const end = Number(endIndex)
if (!Number.isFinite(start) || !Number.isFinite(end)) {
throw new Error('startIndex and endIndex are required')
}
if (end <= start) {
throw new Error('endIndex must be greater than startIndex')
}
return { startIndex: start, endIndex: end }
}
/**
* Build canonical Google Docs metadata from a batchUpdate response. The
* `documentId` is taken from the response body when present, otherwise parsed
* from the request URL (`.../documents/{id}:batchUpdate`).
*/
export function buildBatchUpdateMetadata(
data: { documentId?: string },
responseUrl: string
): { documentId: string; title: string; mimeType: string; url: string } {
let documentId = data.documentId ?? ''
if (!documentId) {
const urlParts = responseUrl.split('/')
for (let i = 0; i < urlParts.length; i++) {
if (urlParts[i] === 'documents' && i + 1 < urlParts.length) {
documentId = urlParts[i + 1].split(':')[0]
break
}
}
}
return {
documentId,
title: 'Updated Document',
mimeType: 'application/vnd.google-apps.document',
url: `https://docs.google.com/document/d/${documentId}/edit`,
}
}
// Helper function to extract text content from Google Docs document structure
export function extractTextFromDocument(document: any): string {
let text = ''
if (!document.body || !document.body.content) {
return text
}
// Process each structural element in the document
for (const element of document.body.content) {
if (element.paragraph) {
for (const paragraphElement of element.paragraph.elements) {
if (paragraphElement.textRun?.content) {
text += paragraphElement.textRun.content
}
}
} else if (element.table) {
// Process tables if needed
for (const tableRow of element.table.tableRows) {
for (const tableCell of tableRow.tableCells) {
if (tableCell.content) {
for (const cellContent of tableCell.content) {
if (cellContent.paragraph) {
for (const paragraphElement of cellContent.paragraph.elements) {
if (paragraphElement.textRun?.content) {
text += paragraphElement.textRun.content
}
}
}
}
}
}
}
}
}
return text
}