d25d482dc2
Publish CLI Package / publish-npm (push) Waiting to run
Publish Python SDK / publish-pypi (push) Waiting to run
Publish TypeScript SDK / publish-npm (push) Waiting to run
CI / Migrate Dev DB (push) Has been skipped
CI / Detect Version (push) Has been cancelled
CI / Migrate DB (push) Has been cancelled
CI / Build Dev ECR (./docker/app.Dockerfile, ECR_APP) (push) Has been cancelled
CI / Build Dev ECR (./docker/db.Dockerfile, ECR_MIGRATIONS) (push) Has been cancelled
CI / Build Dev ECR (./docker/pii.Dockerfile, ECR_PII) (push) Has been cancelled
CI / Build Dev ECR (./docker/realtime.Dockerfile, ECR_REALTIME) (push) Has been cancelled
CI / Deploy Trigger.dev (Dev) (push) Has been cancelled
CI / Build AMD64 (./docker/app.Dockerfile, ECR_APP, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build AMD64 (./docker/db.Dockerfile, ECR_MIGRATIONS, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build AMD64 (./docker/pii.Dockerfile, ECR_PII, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build AMD64 (./docker/realtime.Dockerfile, ECR_REALTIME, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/app.Dockerfile, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/db.Dockerfile, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/pii.Dockerfile, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/realtime.Dockerfile, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Check Docs Changes (push) Has been cancelled
CI / Process Docs (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
CI / Test and Build (push) Has been cancelled
135 lines
4.4 KiB
TypeScript
135 lines
4.4 KiB
TypeScript
/**
|
|
* 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
|
|
}
|