chore: import upstream snapshot with attribution
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
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
This commit is contained in:
@@ -0,0 +1,188 @@
|
||||
import type {
|
||||
GoogleForm,
|
||||
GoogleFormsBatchUpdateParams,
|
||||
GoogleFormsBatchUpdateResponse,
|
||||
} from '@/tools/google_forms/types'
|
||||
import { buildBatchUpdateUrl, getGoogleFormsErrorMessage } from '@/tools/google_forms/utils'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
interface BatchUpdateApiResponse {
|
||||
replies?: Record<string, unknown>[]
|
||||
writeControl?: {
|
||||
requiredRevisionId?: string
|
||||
targetRevisionId?: string
|
||||
}
|
||||
form?: GoogleForm
|
||||
}
|
||||
|
||||
export const batchUpdateTool: ToolConfig<
|
||||
GoogleFormsBatchUpdateParams,
|
||||
GoogleFormsBatchUpdateResponse
|
||||
> = {
|
||||
id: 'google_forms_batch_update',
|
||||
name: 'Google Forms: Batch Update',
|
||||
description: 'Apply multiple updates to a form (add items, update info, change settings, etc.)',
|
||||
version: '1.0.0',
|
||||
|
||||
oauth: {
|
||||
required: true,
|
||||
provider: 'google-forms',
|
||||
},
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'hidden',
|
||||
description: 'OAuth access token',
|
||||
},
|
||||
formId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Google Forms form ID',
|
||||
},
|
||||
requests: {
|
||||
type: 'json',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Array of update requests (updateFormInfo, updateSettings, createItem, updateItem, moveItem, deleteItem)',
|
||||
},
|
||||
includeFormInResponse: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Whether to return the updated form in the response',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params: GoogleFormsBatchUpdateParams) => buildBatchUpdateUrl(params.formId),
|
||||
method: 'POST',
|
||||
headers: (params: GoogleFormsBatchUpdateParams) => ({
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
body: (params: GoogleFormsBatchUpdateParams) => ({
|
||||
requests: params.requests,
|
||||
includeFormInResponse: params.includeFormInResponse ?? false,
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = (await response.json()) as BatchUpdateApiResponse
|
||||
|
||||
if (!response.ok) {
|
||||
return {
|
||||
success: false,
|
||||
output: {
|
||||
replies: [],
|
||||
writeControl: null,
|
||||
form: null,
|
||||
},
|
||||
error: getGoogleFormsErrorMessage(data, 'Failed to batch update form'),
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
replies: data.replies ?? [],
|
||||
writeControl: data.writeControl ?? null,
|
||||
form: data.form ?? null,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
replies: {
|
||||
type: 'array',
|
||||
description: 'The replies from each update request',
|
||||
items: {
|
||||
type: 'json',
|
||||
},
|
||||
},
|
||||
writeControl: {
|
||||
type: 'object',
|
||||
description: 'Write control information with revision IDs',
|
||||
optional: true,
|
||||
properties: {
|
||||
requiredRevisionId: {
|
||||
type: 'string',
|
||||
description: 'Required revision ID for conflict detection',
|
||||
},
|
||||
targetRevisionId: { type: 'string', description: 'Target revision ID' },
|
||||
},
|
||||
},
|
||||
form: {
|
||||
type: 'object',
|
||||
description: 'The updated form (if includeFormInResponse was true)',
|
||||
optional: true,
|
||||
properties: {
|
||||
formId: { type: 'string', description: 'The form ID' },
|
||||
info: {
|
||||
type: 'object',
|
||||
description: 'Form info containing title and description',
|
||||
properties: {
|
||||
title: { type: 'string', description: 'The form title visible to responders' },
|
||||
description: { type: 'string', description: 'The form description' },
|
||||
documentTitle: { type: 'string', description: 'The document title visible in Drive' },
|
||||
},
|
||||
},
|
||||
settings: {
|
||||
type: 'object',
|
||||
description: 'Form settings',
|
||||
properties: {
|
||||
quizSettings: {
|
||||
type: 'object',
|
||||
description: 'Quiz settings',
|
||||
properties: {
|
||||
isQuiz: { type: 'boolean', description: 'Whether the form is a quiz' },
|
||||
},
|
||||
},
|
||||
emailCollectionType: { type: 'string', description: 'Email collection type' },
|
||||
},
|
||||
},
|
||||
items: {
|
||||
type: 'array',
|
||||
description: 'The form items (questions, sections, etc.)',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
itemId: { type: 'string', description: 'Item ID' },
|
||||
title: { type: 'string', description: 'Item title' },
|
||||
description: { type: 'string', description: 'Item description' },
|
||||
questionItem: { type: 'json', description: 'Question item configuration' },
|
||||
questionGroupItem: { type: 'json', description: 'Question group configuration' },
|
||||
pageBreakItem: { type: 'json', description: 'Page break configuration' },
|
||||
textItem: { type: 'json', description: 'Text item configuration' },
|
||||
imageItem: { type: 'json', description: 'Image item configuration' },
|
||||
videoItem: { type: 'json', description: 'Video item configuration' },
|
||||
},
|
||||
},
|
||||
},
|
||||
revisionId: { type: 'string', description: 'The revision ID of the form' },
|
||||
responderUri: { type: 'string', description: 'The URI to share with responders' },
|
||||
linkedSheetId: { type: 'string', description: 'The ID of the linked Google Sheet' },
|
||||
publishSettings: {
|
||||
type: 'object',
|
||||
description: 'Form publish settings',
|
||||
properties: {
|
||||
publishState: {
|
||||
type: 'object',
|
||||
description: 'Current publish state',
|
||||
properties: {
|
||||
isPublished: { type: 'boolean', description: 'Whether the form is published' },
|
||||
isAcceptingResponses: {
|
||||
type: 'boolean',
|
||||
description: 'Whether the form is accepting responses',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
import type {
|
||||
GoogleForm,
|
||||
GoogleFormsCreateFormParams,
|
||||
GoogleFormsCreateFormResponse,
|
||||
} from '@/tools/google_forms/types'
|
||||
import { buildCreateFormUrl, getGoogleFormsErrorMessage } from '@/tools/google_forms/utils'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const createFormTool: ToolConfig<
|
||||
GoogleFormsCreateFormParams,
|
||||
GoogleFormsCreateFormResponse
|
||||
> = {
|
||||
id: 'google_forms_create_form',
|
||||
name: 'Google Forms: Create Form',
|
||||
description: 'Create a new Google Form with a title',
|
||||
version: '1.0.0',
|
||||
|
||||
oauth: {
|
||||
required: true,
|
||||
provider: 'google-forms',
|
||||
},
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'hidden',
|
||||
description: 'OAuth access token',
|
||||
},
|
||||
title: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The title of the form visible to responders',
|
||||
},
|
||||
documentTitle: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The document title visible in Drive (defaults to form title)',
|
||||
},
|
||||
unpublished: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'If true, create an unpublished form that does not accept responses',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params: GoogleFormsCreateFormParams) => buildCreateFormUrl(params.unpublished),
|
||||
method: 'POST',
|
||||
headers: (params: GoogleFormsCreateFormParams) => ({
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
body: (params: GoogleFormsCreateFormParams) => ({
|
||||
info: {
|
||||
title: params.title,
|
||||
...(params.documentTitle ? { documentTitle: params.documentTitle } : {}),
|
||||
},
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = (await response.json()) as GoogleForm
|
||||
|
||||
if (!response.ok) {
|
||||
return {
|
||||
success: false,
|
||||
output: {
|
||||
formId: '',
|
||||
title: null,
|
||||
documentTitle: null,
|
||||
responderUri: null,
|
||||
revisionId: null,
|
||||
},
|
||||
error: getGoogleFormsErrorMessage(data, 'Failed to create form'),
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
formId: data.formId ?? '',
|
||||
title: data.info?.title ?? null,
|
||||
documentTitle: data.info?.documentTitle ?? null,
|
||||
responderUri: data.responderUri ?? null,
|
||||
revisionId: data.revisionId ?? null,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
formId: { type: 'string', description: 'The ID of the created form' },
|
||||
title: { type: 'string', description: 'The form title', optional: true },
|
||||
documentTitle: { type: 'string', description: 'The document title in Drive', optional: true },
|
||||
responderUri: {
|
||||
type: 'string',
|
||||
description: 'The URI to share with responders',
|
||||
optional: true,
|
||||
},
|
||||
revisionId: { type: 'string', description: 'The revision ID of the form', optional: true },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
import type {
|
||||
GoogleFormsCreateWatchParams,
|
||||
GoogleFormsCreateWatchResponse,
|
||||
GoogleFormsWatch,
|
||||
} from '@/tools/google_forms/types'
|
||||
import { buildCreateWatchUrl, getGoogleFormsErrorMessage } from '@/tools/google_forms/utils'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const createWatchTool: ToolConfig<
|
||||
GoogleFormsCreateWatchParams,
|
||||
GoogleFormsCreateWatchResponse
|
||||
> = {
|
||||
id: 'google_forms_create_watch',
|
||||
name: 'Google Forms: Create Watch',
|
||||
description: 'Create a notification watch for form changes (schema changes or new responses)',
|
||||
version: '1.0.0',
|
||||
|
||||
oauth: {
|
||||
required: true,
|
||||
provider: 'google-forms',
|
||||
},
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'hidden',
|
||||
description: 'OAuth access token',
|
||||
},
|
||||
formId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Google Forms form ID to watch',
|
||||
},
|
||||
eventType: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Event type to watch: SCHEMA (form changes) or RESPONSES (new submissions)',
|
||||
},
|
||||
topicName: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'The Cloud Pub/Sub topic name (format: projects/{project}/topics/{topic})',
|
||||
},
|
||||
watchId: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Custom watch ID (4-63 chars, lowercase letters, numbers, hyphens)',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params: GoogleFormsCreateWatchParams) => buildCreateWatchUrl(params.formId),
|
||||
method: 'POST',
|
||||
headers: (params: GoogleFormsCreateWatchParams) => ({
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
body: (params: GoogleFormsCreateWatchParams) => ({
|
||||
watch: {
|
||||
target: {
|
||||
topic: {
|
||||
topicName: params.topicName,
|
||||
},
|
||||
},
|
||||
eventType: params.eventType,
|
||||
},
|
||||
...(params.watchId ? { watchId: params.watchId } : {}),
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = (await response.json()) as GoogleFormsWatch
|
||||
|
||||
if (!response.ok) {
|
||||
return {
|
||||
success: false,
|
||||
output: {
|
||||
id: '',
|
||||
eventType: '',
|
||||
topicName: null,
|
||||
createTime: null,
|
||||
expireTime: null,
|
||||
state: null,
|
||||
},
|
||||
error: getGoogleFormsErrorMessage(data, 'Failed to create watch'),
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
id: data.id ?? '',
|
||||
eventType: data.eventType ?? '',
|
||||
topicName: data.target?.topic?.topicName ?? null,
|
||||
createTime: data.createTime ?? null,
|
||||
expireTime: data.expireTime ?? null,
|
||||
state: data.state ?? null,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
id: { type: 'string', description: 'The watch ID' },
|
||||
eventType: { type: 'string', description: 'The event type being watched' },
|
||||
topicName: { type: 'string', description: 'The Cloud Pub/Sub topic', optional: true },
|
||||
createTime: { type: 'string', description: 'When the watch was created', optional: true },
|
||||
expireTime: {
|
||||
type: 'string',
|
||||
description: 'When the watch expires (7 days after creation)',
|
||||
optional: true,
|
||||
},
|
||||
state: { type: 'string', description: 'The watch state (ACTIVE, SUSPENDED)', optional: true },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
import type {
|
||||
GoogleFormsDeleteWatchParams,
|
||||
GoogleFormsDeleteWatchResponse,
|
||||
} from '@/tools/google_forms/types'
|
||||
import { buildDeleteWatchUrl } from '@/tools/google_forms/utils'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const deleteWatchTool: ToolConfig<
|
||||
GoogleFormsDeleteWatchParams,
|
||||
GoogleFormsDeleteWatchResponse
|
||||
> = {
|
||||
id: 'google_forms_delete_watch',
|
||||
name: 'Google Forms: Delete Watch',
|
||||
description: 'Delete a notification watch from a form',
|
||||
version: '1.0.0',
|
||||
|
||||
oauth: {
|
||||
required: true,
|
||||
provider: 'google-forms',
|
||||
},
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'hidden',
|
||||
description: 'OAuth access token',
|
||||
},
|
||||
formId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Google Forms form ID',
|
||||
},
|
||||
watchId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Watch ID to delete',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params: GoogleFormsDeleteWatchParams) =>
|
||||
buildDeleteWatchUrl(params.formId, params.watchId),
|
||||
method: 'DELETE',
|
||||
headers: (params: GoogleFormsDeleteWatchParams) => ({
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
if (!response.ok) {
|
||||
const data = (await response.json()) as { error?: { message?: string } }
|
||||
return {
|
||||
success: false,
|
||||
output: {
|
||||
deleted: false,
|
||||
},
|
||||
error: data.error?.message ?? 'Failed to delete watch',
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
deleted: true,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
deleted: { type: 'boolean', description: 'Whether the watch was successfully deleted' },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
import type {
|
||||
GoogleForm,
|
||||
GoogleFormsGetFormParams,
|
||||
GoogleFormsGetFormResponse,
|
||||
} from '@/tools/google_forms/types'
|
||||
import { buildGetFormUrl, getGoogleFormsErrorMessage } from '@/tools/google_forms/utils'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const getFormTool: ToolConfig<GoogleFormsGetFormParams, GoogleFormsGetFormResponse> = {
|
||||
id: 'google_forms_get_form',
|
||||
name: 'Google Forms: Get Form',
|
||||
description: 'Retrieve a form structure including its items, settings, and metadata',
|
||||
version: '1.0.0',
|
||||
|
||||
oauth: {
|
||||
required: true,
|
||||
provider: 'google-forms',
|
||||
},
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'hidden',
|
||||
description: 'OAuth access token',
|
||||
},
|
||||
formId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Google Forms form ID to retrieve',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params: GoogleFormsGetFormParams) => buildGetFormUrl(params.formId),
|
||||
method: 'GET',
|
||||
headers: (params: GoogleFormsGetFormParams) => ({
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = (await response.json()) as GoogleForm
|
||||
|
||||
if (!response.ok) {
|
||||
return {
|
||||
success: false,
|
||||
output: {
|
||||
formId: '',
|
||||
title: null,
|
||||
description: null,
|
||||
documentTitle: null,
|
||||
responderUri: null,
|
||||
linkedSheetId: null,
|
||||
revisionId: null,
|
||||
items: [],
|
||||
settings: null,
|
||||
publishSettings: null,
|
||||
},
|
||||
error: getGoogleFormsErrorMessage(data, 'Failed to get form'),
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
formId: data.formId ?? '',
|
||||
title: data.info?.title ?? null,
|
||||
description: data.info?.description ?? null,
|
||||
documentTitle: data.info?.documentTitle ?? null,
|
||||
responderUri: data.responderUri ?? null,
|
||||
linkedSheetId: data.linkedSheetId ?? null,
|
||||
revisionId: data.revisionId ?? null,
|
||||
items: data.items ?? [],
|
||||
settings: data.settings ?? null,
|
||||
publishSettings: data.publishSettings ?? null,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
formId: { type: 'string', description: 'The form ID' },
|
||||
title: { type: 'string', description: 'The form title visible to responders', optional: true },
|
||||
description: { type: 'string', description: 'The form description', optional: true },
|
||||
documentTitle: {
|
||||
type: 'string',
|
||||
description: 'The document title visible in Drive',
|
||||
optional: true,
|
||||
},
|
||||
responderUri: {
|
||||
type: 'string',
|
||||
description: 'The URI to share with responders',
|
||||
optional: true,
|
||||
},
|
||||
linkedSheetId: {
|
||||
type: 'string',
|
||||
description: 'The ID of the linked Google Sheet',
|
||||
optional: true,
|
||||
},
|
||||
revisionId: { type: 'string', description: 'The revision ID of the form', optional: true },
|
||||
items: {
|
||||
type: 'array',
|
||||
description: 'The form items (questions, sections, etc.)',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
itemId: { type: 'string', description: 'Item ID' },
|
||||
title: { type: 'string', description: 'Item title' },
|
||||
description: { type: 'string', description: 'Item description' },
|
||||
},
|
||||
},
|
||||
},
|
||||
settings: { type: 'json', description: 'Form settings', optional: true },
|
||||
publishSettings: { type: 'json', description: 'Form publish settings', optional: true },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,230 @@
|
||||
import type {
|
||||
GoogleFormsGetResponsesParams,
|
||||
GoogleFormsResponse,
|
||||
GoogleFormsResponseList,
|
||||
} from '@/tools/google_forms/types'
|
||||
import { buildGetResponseUrl, buildListResponsesUrl } from '@/tools/google_forms/utils'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const getResponsesTool: ToolConfig<GoogleFormsGetResponsesParams> = {
|
||||
id: 'google_forms_get_responses',
|
||||
name: 'Google Forms: Get Responses',
|
||||
description: 'Retrieve a single response or list responses from a Google Form',
|
||||
version: '1.0.0',
|
||||
|
||||
oauth: {
|
||||
required: true,
|
||||
provider: 'google-forms',
|
||||
},
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'hidden',
|
||||
description: 'OAuth2 access token',
|
||||
},
|
||||
formId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Google Forms form ID',
|
||||
},
|
||||
responseId: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Response ID - if provided, returns this specific response',
|
||||
},
|
||||
pageSize: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Maximum number of responses to return (service may return fewer). Defaults to 5000.',
|
||||
},
|
||||
pageToken: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Page token from a previous list response to fetch the next page of responses',
|
||||
},
|
||||
filter: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Filter responses, e.g. "timestamp > 2024-01-01T00:00:00Z" (RFC3339 UTC). Only timestamp filters are supported.',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params: GoogleFormsGetResponsesParams) =>
|
||||
params.responseId
|
||||
? buildGetResponseUrl({ formId: params.formId, responseId: params.responseId })
|
||||
: buildListResponsesUrl({
|
||||
formId: params.formId,
|
||||
pageSize: params.pageSize ? Number(params.pageSize) : undefined,
|
||||
pageToken: params.pageToken,
|
||||
filter: params.filter,
|
||||
}),
|
||||
method: 'GET',
|
||||
headers: (params: GoogleFormsGetResponsesParams) => ({
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response, params?: GoogleFormsGetResponsesParams) => {
|
||||
const data = (await response.json()) as unknown
|
||||
|
||||
if (!response.ok) {
|
||||
let errorMessage = response.statusText || 'Failed to fetch responses'
|
||||
if (data && typeof data === 'object') {
|
||||
const record = data as Record<string, unknown>
|
||||
const error = record.error as { message?: string } | undefined
|
||||
if (error?.message) {
|
||||
errorMessage = error.message
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
success: false,
|
||||
output: (data as Record<string, unknown>) || {},
|
||||
error: errorMessage,
|
||||
}
|
||||
}
|
||||
|
||||
// Normalize answers into a flat key/value map per response
|
||||
const normalizeAnswerContainer = (container: unknown): unknown => {
|
||||
if (!container || typeof container !== 'object') return container
|
||||
const record = container as Record<string, unknown>
|
||||
const answers = record.answers as unknown[] | undefined
|
||||
if (Array.isArray(answers)) {
|
||||
const values = answers.map((entry) => {
|
||||
if (entry && typeof entry === 'object') {
|
||||
const er = entry as Record<string, unknown>
|
||||
if (typeof er.value !== 'undefined') return er.value
|
||||
}
|
||||
return entry
|
||||
})
|
||||
return values.length === 1 ? values[0] : values
|
||||
}
|
||||
return container
|
||||
}
|
||||
|
||||
const normalizeAnswers = (answers: unknown): Record<string, unknown> => {
|
||||
if (!answers || typeof answers !== 'object') return {}
|
||||
const src = answers as Record<string, unknown>
|
||||
const out: Record<string, unknown> = {}
|
||||
for (const [questionId, answerObj] of Object.entries(src)) {
|
||||
if (answerObj && typeof answerObj === 'object') {
|
||||
const aRec = answerObj as Record<string, unknown>
|
||||
// Find first *Answers property that contains an answers array
|
||||
const key = Object.keys(aRec).find(
|
||||
(k) => k.toLowerCase().endsWith('answers') && Array.isArray((aRec[k] as any)?.answers)
|
||||
)
|
||||
if (key) {
|
||||
out[questionId] = normalizeAnswerContainer(aRec[key])
|
||||
continue
|
||||
}
|
||||
}
|
||||
out[questionId] = answerObj as unknown
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
const normalizeResponse = (r: GoogleFormsResponse): Record<string, unknown> => ({
|
||||
responseId: r.responseId,
|
||||
createTime: r.createTime,
|
||||
lastSubmittedTime: r.lastSubmittedTime,
|
||||
answers: normalizeAnswers(r.answers as unknown),
|
||||
})
|
||||
|
||||
// Distinguish single vs list response shapes
|
||||
const isList = (obj: unknown): obj is GoogleFormsResponseList =>
|
||||
!!obj && typeof obj === 'object' && Array.isArray((obj as GoogleFormsResponseList).responses)
|
||||
|
||||
if (isList(data)) {
|
||||
const listData = data as GoogleFormsResponseList
|
||||
const toTimestamp = (s?: string): number => {
|
||||
if (!s) return 0
|
||||
const t = Date.parse(s)
|
||||
return Number.isNaN(t) ? 0 : t
|
||||
}
|
||||
const sorted = (listData.responses || [])
|
||||
.slice()
|
||||
.sort(
|
||||
(a, b) =>
|
||||
toTimestamp(b.lastSubmittedTime || b.createTime) -
|
||||
toTimestamp(a.lastSubmittedTime || a.createTime)
|
||||
)
|
||||
const normalized = sorted.map((r) => normalizeResponse(r))
|
||||
const output: Record<string, unknown> = {
|
||||
responses: normalized,
|
||||
nextPageToken: listData.nextPageToken ?? null,
|
||||
raw: listData,
|
||||
}
|
||||
return {
|
||||
success: true,
|
||||
output,
|
||||
}
|
||||
}
|
||||
|
||||
const single = data as GoogleFormsResponse
|
||||
const normalizedSingle = normalizeResponse(single)
|
||||
const output: Record<string, unknown> = {
|
||||
response: normalizedSingle,
|
||||
raw: single,
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output,
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
responses: {
|
||||
type: 'array',
|
||||
description: 'Array of form responses (when no responseId provided)',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
responseId: { type: 'string', description: 'Unique response ID' },
|
||||
createTime: { type: 'string', description: 'When the response was created' },
|
||||
lastSubmittedTime: {
|
||||
type: 'string',
|
||||
description: 'When the response was last submitted',
|
||||
},
|
||||
answers: {
|
||||
type: 'json',
|
||||
description: 'Map of question IDs to answer values',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
nextPageToken: {
|
||||
type: 'string',
|
||||
description: 'Token to fetch the next page of responses (null when no more pages)',
|
||||
optional: true,
|
||||
},
|
||||
response: {
|
||||
type: 'object',
|
||||
description: 'Single form response (when responseId is provided)',
|
||||
properties: {
|
||||
responseId: { type: 'string', description: 'Unique response ID' },
|
||||
createTime: { type: 'string', description: 'When the response was created' },
|
||||
lastSubmittedTime: { type: 'string', description: 'When the response was last submitted' },
|
||||
answers: {
|
||||
type: 'json',
|
||||
description: 'Map of question IDs to answer values',
|
||||
},
|
||||
},
|
||||
},
|
||||
raw: {
|
||||
type: 'json',
|
||||
description: 'Raw API response data',
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import { batchUpdateTool } from '@/tools/google_forms/batch_update'
|
||||
import { createFormTool } from '@/tools/google_forms/create_form'
|
||||
import { createWatchTool } from '@/tools/google_forms/create_watch'
|
||||
import { deleteWatchTool } from '@/tools/google_forms/delete_watch'
|
||||
import { getFormTool } from '@/tools/google_forms/get_form'
|
||||
import { getResponsesTool } from '@/tools/google_forms/get_responses'
|
||||
import { listWatchesTool } from '@/tools/google_forms/list_watches'
|
||||
import { renewWatchTool } from '@/tools/google_forms/renew_watch'
|
||||
import { setPublishSettingsTool } from '@/tools/google_forms/set_publish_settings'
|
||||
|
||||
export const googleFormsGetResponsesTool = getResponsesTool
|
||||
export const googleFormsGetFormTool = getFormTool
|
||||
export const googleFormsCreateFormTool = createFormTool
|
||||
export const googleFormsBatchUpdateTool = batchUpdateTool
|
||||
export const googleFormsSetPublishSettingsTool = setPublishSettingsTool
|
||||
export const googleFormsCreateWatchTool = createWatchTool
|
||||
export const googleFormsListWatchesTool = listWatchesTool
|
||||
export const googleFormsDeleteWatchTool = deleteWatchTool
|
||||
export const googleFormsRenewWatchTool = renewWatchTool
|
||||
|
||||
export * from './types'
|
||||
@@ -0,0 +1,98 @@
|
||||
import type {
|
||||
GoogleFormsListWatchesParams,
|
||||
GoogleFormsListWatchesResponse,
|
||||
GoogleFormsWatch,
|
||||
} from '@/tools/google_forms/types'
|
||||
import { buildListWatchesUrl, getGoogleFormsErrorMessage } from '@/tools/google_forms/utils'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
interface ListWatchesApiResponse {
|
||||
watches?: GoogleFormsWatch[]
|
||||
}
|
||||
|
||||
export const listWatchesTool: ToolConfig<
|
||||
GoogleFormsListWatchesParams,
|
||||
GoogleFormsListWatchesResponse
|
||||
> = {
|
||||
id: 'google_forms_list_watches',
|
||||
name: 'Google Forms: List Watches',
|
||||
description: 'List all notification watches for a form',
|
||||
version: '1.0.0',
|
||||
|
||||
oauth: {
|
||||
required: true,
|
||||
provider: 'google-forms',
|
||||
},
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'hidden',
|
||||
description: 'OAuth access token',
|
||||
},
|
||||
formId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Google Forms form ID',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params: GoogleFormsListWatchesParams) => buildListWatchesUrl(params.formId),
|
||||
method: 'GET',
|
||||
headers: (params: GoogleFormsListWatchesParams) => ({
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = (await response.json()) as ListWatchesApiResponse
|
||||
|
||||
if (!response.ok) {
|
||||
return {
|
||||
success: false,
|
||||
output: {
|
||||
watches: [],
|
||||
},
|
||||
error: getGoogleFormsErrorMessage(data, 'Failed to list watches'),
|
||||
}
|
||||
}
|
||||
|
||||
const watches = (data.watches ?? []).map((watch) => ({
|
||||
id: watch.id,
|
||||
target: watch.target,
|
||||
eventType: watch.eventType,
|
||||
createTime: watch.createTime,
|
||||
expireTime: watch.expireTime,
|
||||
state: watch.state,
|
||||
errorType: watch.errorType,
|
||||
}))
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
watches,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
watches: {
|
||||
type: 'array',
|
||||
description: 'List of watches for the form',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'string', description: 'Watch ID' },
|
||||
eventType: { type: 'string', description: 'Event type (SCHEMA or RESPONSES)' },
|
||||
createTime: { type: 'string', description: 'When the watch was created' },
|
||||
expireTime: { type: 'string', description: 'When the watch expires' },
|
||||
state: { type: 'string', description: 'Watch state' },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
import type {
|
||||
GoogleFormsRenewWatchParams,
|
||||
GoogleFormsRenewWatchResponse,
|
||||
GoogleFormsWatch,
|
||||
} from '@/tools/google_forms/types'
|
||||
import { buildRenewWatchUrl, getGoogleFormsErrorMessage } from '@/tools/google_forms/utils'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const renewWatchTool: ToolConfig<
|
||||
GoogleFormsRenewWatchParams,
|
||||
GoogleFormsRenewWatchResponse
|
||||
> = {
|
||||
id: 'google_forms_renew_watch',
|
||||
name: 'Google Forms: Renew Watch',
|
||||
description: 'Renew a notification watch for another 7 days',
|
||||
version: '1.0.0',
|
||||
|
||||
oauth: {
|
||||
required: true,
|
||||
provider: 'google-forms',
|
||||
},
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'hidden',
|
||||
description: 'OAuth access token',
|
||||
},
|
||||
formId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Google Forms form ID',
|
||||
},
|
||||
watchId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Watch ID to renew',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params: GoogleFormsRenewWatchParams) => buildRenewWatchUrl(params.formId, params.watchId),
|
||||
method: 'POST',
|
||||
headers: (params: GoogleFormsRenewWatchParams) => ({
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = (await response.json()) as GoogleFormsWatch
|
||||
|
||||
if (!response.ok) {
|
||||
return {
|
||||
success: false,
|
||||
output: {
|
||||
id: '',
|
||||
eventType: null,
|
||||
expireTime: null,
|
||||
state: null,
|
||||
},
|
||||
error: getGoogleFormsErrorMessage(data, 'Failed to renew watch'),
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
id: data.id ?? '',
|
||||
eventType: data.eventType ?? null,
|
||||
expireTime: data.expireTime ?? null,
|
||||
state: data.state ?? null,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
id: { type: 'string', description: 'The watch ID' },
|
||||
eventType: { type: 'string', description: 'The event type being watched', optional: true },
|
||||
expireTime: { type: 'string', description: 'The new expiration time', optional: true },
|
||||
state: { type: 'string', description: 'The watch state', optional: true },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
import type {
|
||||
GoogleFormsPublishSettings,
|
||||
GoogleFormsSetPublishSettingsParams,
|
||||
GoogleFormsSetPublishSettingsResponse,
|
||||
} from '@/tools/google_forms/types'
|
||||
import { buildSetPublishSettingsUrl, getGoogleFormsErrorMessage } from '@/tools/google_forms/utils'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
interface SetPublishSettingsApiResponse {
|
||||
formId?: string
|
||||
publishSettings?: GoogleFormsPublishSettings
|
||||
}
|
||||
|
||||
export const setPublishSettingsTool: ToolConfig<
|
||||
GoogleFormsSetPublishSettingsParams,
|
||||
GoogleFormsSetPublishSettingsResponse
|
||||
> = {
|
||||
id: 'google_forms_set_publish_settings',
|
||||
name: 'Google Forms: Set Publish Settings',
|
||||
description: 'Update the publish settings of a form (publish/unpublish, accept responses)',
|
||||
version: '1.0.0',
|
||||
|
||||
oauth: {
|
||||
required: true,
|
||||
provider: 'google-forms',
|
||||
},
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'hidden',
|
||||
description: 'OAuth access token',
|
||||
},
|
||||
formId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Google Forms form ID',
|
||||
},
|
||||
isPublished: {
|
||||
type: 'boolean',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Whether the form is published and visible to others',
|
||||
},
|
||||
isAcceptingResponses: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Whether the form accepts responses (forced to false if isPublished is false)',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params: GoogleFormsSetPublishSettingsParams) => buildSetPublishSettingsUrl(params.formId),
|
||||
method: 'POST',
|
||||
headers: (params: GoogleFormsSetPublishSettingsParams) => ({
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
body: (params: GoogleFormsSetPublishSettingsParams) => ({
|
||||
publishSettings: {
|
||||
publishState: {
|
||||
isPublished: params.isPublished,
|
||||
...(params.isAcceptingResponses !== undefined
|
||||
? { isAcceptingResponses: params.isAcceptingResponses }
|
||||
: {}),
|
||||
},
|
||||
},
|
||||
updateMask: 'publishState',
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = (await response.json()) as SetPublishSettingsApiResponse
|
||||
|
||||
if (!response.ok) {
|
||||
return {
|
||||
success: false,
|
||||
output: {
|
||||
formId: '',
|
||||
publishSettings: {},
|
||||
},
|
||||
error: getGoogleFormsErrorMessage(data, 'Failed to set publish settings'),
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
formId: data.formId ?? '',
|
||||
publishSettings: data.publishSettings ?? {},
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
formId: { type: 'string', description: 'The form ID' },
|
||||
publishSettings: {
|
||||
type: 'json',
|
||||
description: 'The updated publish settings',
|
||||
properties: {
|
||||
publishState: {
|
||||
type: 'object',
|
||||
description: 'The publish state',
|
||||
properties: {
|
||||
isPublished: { type: 'boolean', description: 'Whether the form is published' },
|
||||
isAcceptingResponses: {
|
||||
type: 'boolean',
|
||||
description: 'Whether the form accepts responses',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,270 @@
|
||||
import type { ToolResponse } from '@/tools/types'
|
||||
|
||||
// ============================================
|
||||
// Common Types
|
||||
// ============================================
|
||||
|
||||
export interface GoogleFormsResponse {
|
||||
responseId?: string
|
||||
createTime?: string
|
||||
lastSubmittedTime?: string
|
||||
answers?: Record<string, unknown>
|
||||
respondentEmail?: string
|
||||
totalScore?: number
|
||||
[key: string]: unknown
|
||||
}
|
||||
|
||||
export interface GoogleFormsResponseList {
|
||||
responses?: GoogleFormsResponse[]
|
||||
nextPageToken?: string
|
||||
}
|
||||
|
||||
interface GoogleFormsInfo {
|
||||
title?: string
|
||||
description?: string
|
||||
documentTitle?: string
|
||||
}
|
||||
|
||||
interface GoogleFormsSettings {
|
||||
quizSettings?: {
|
||||
isQuiz?: boolean
|
||||
}
|
||||
emailCollectionType?:
|
||||
| 'EMAIL_COLLECTION_TYPE_UNSPECIFIED'
|
||||
| 'DO_NOT_COLLECT'
|
||||
| 'VERIFIED'
|
||||
| 'RESPONDER_INPUT'
|
||||
[key: string]: unknown
|
||||
}
|
||||
|
||||
interface GoogleFormsPublishState {
|
||||
isPublished?: boolean
|
||||
isAcceptingResponses?: boolean
|
||||
}
|
||||
|
||||
export interface GoogleFormsPublishSettings {
|
||||
publishState?: GoogleFormsPublishState
|
||||
}
|
||||
|
||||
interface GoogleFormsItem {
|
||||
itemId?: string
|
||||
title?: string
|
||||
description?: string
|
||||
questionItem?: Record<string, unknown>
|
||||
questionGroupItem?: Record<string, unknown>
|
||||
pageBreakItem?: Record<string, unknown>
|
||||
textItem?: Record<string, unknown>
|
||||
imageItem?: Record<string, unknown>
|
||||
videoItem?: Record<string, unknown>
|
||||
}
|
||||
|
||||
export interface GoogleForm {
|
||||
formId?: string
|
||||
info?: GoogleFormsInfo
|
||||
settings?: GoogleFormsSettings
|
||||
items?: GoogleFormsItem[]
|
||||
revisionId?: string
|
||||
responderUri?: string
|
||||
linkedSheetId?: string
|
||||
publishSettings?: GoogleFormsPublishSettings
|
||||
}
|
||||
|
||||
export interface GoogleFormsWatch {
|
||||
id?: string
|
||||
target?: {
|
||||
topic?: {
|
||||
topicName?: string
|
||||
}
|
||||
}
|
||||
eventType?: 'EVENT_TYPE_UNSPECIFIED' | 'SCHEMA' | 'RESPONSES'
|
||||
createTime?: string
|
||||
expireTime?: string
|
||||
state?: 'STATE_UNSPECIFIED' | 'ACTIVE' | 'SUSPENDED'
|
||||
errorType?: string
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// Get Responses Params
|
||||
// ============================================
|
||||
|
||||
export interface GoogleFormsGetResponsesParams {
|
||||
accessToken: string
|
||||
formId: string
|
||||
responseId?: string
|
||||
pageSize?: number
|
||||
pageToken?: string
|
||||
filter?: string
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// Get Form Params & Response
|
||||
// ============================================
|
||||
|
||||
export interface GoogleFormsGetFormParams {
|
||||
accessToken: string
|
||||
formId: string
|
||||
}
|
||||
|
||||
export interface GoogleFormsGetFormResponse extends ToolResponse {
|
||||
output: {
|
||||
formId: string
|
||||
title: string | null
|
||||
description: string | null
|
||||
documentTitle: string | null
|
||||
responderUri: string | null
|
||||
linkedSheetId: string | null
|
||||
revisionId: string | null
|
||||
items: GoogleFormsItem[]
|
||||
settings: GoogleFormsSettings | null
|
||||
publishSettings: GoogleFormsPublishSettings | null
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// Create Form Params & Response
|
||||
// ============================================
|
||||
|
||||
export interface GoogleFormsCreateFormParams {
|
||||
accessToken: string
|
||||
title: string
|
||||
documentTitle?: string
|
||||
unpublished?: boolean
|
||||
}
|
||||
|
||||
export interface GoogleFormsCreateFormResponse extends ToolResponse {
|
||||
output: {
|
||||
formId: string
|
||||
title: string | null
|
||||
documentTitle: string | null
|
||||
responderUri: string | null
|
||||
revisionId: string | null
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// Batch Update Params & Response
|
||||
// ============================================
|
||||
|
||||
interface GoogleFormsBatchUpdateRequest {
|
||||
updateFormInfo?: {
|
||||
info: Partial<GoogleFormsInfo>
|
||||
updateMask: string
|
||||
}
|
||||
updateSettings?: {
|
||||
settings: Partial<GoogleFormsSettings>
|
||||
updateMask: string
|
||||
}
|
||||
createItem?: {
|
||||
item: GoogleFormsItem
|
||||
location: { index: number }
|
||||
}
|
||||
updateItem?: {
|
||||
item: GoogleFormsItem
|
||||
location: { index: number }
|
||||
updateMask: string
|
||||
}
|
||||
moveItem?: {
|
||||
originalLocation: { index: number }
|
||||
newLocation: { index: number }
|
||||
}
|
||||
deleteItem?: {
|
||||
location: { index: number }
|
||||
}
|
||||
}
|
||||
|
||||
export interface GoogleFormsBatchUpdateParams {
|
||||
accessToken: string
|
||||
formId: string
|
||||
requests: GoogleFormsBatchUpdateRequest[]
|
||||
includeFormInResponse?: boolean
|
||||
}
|
||||
|
||||
export interface GoogleFormsBatchUpdateResponse extends ToolResponse {
|
||||
output: {
|
||||
replies: Record<string, unknown>[]
|
||||
writeControl: {
|
||||
requiredRevisionId?: string
|
||||
targetRevisionId?: string
|
||||
} | null
|
||||
form: GoogleForm | null
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// Set Publish Settings Params & Response
|
||||
// ============================================
|
||||
|
||||
export interface GoogleFormsSetPublishSettingsParams {
|
||||
accessToken: string
|
||||
formId: string
|
||||
isPublished: boolean
|
||||
isAcceptingResponses?: boolean
|
||||
}
|
||||
|
||||
export interface GoogleFormsSetPublishSettingsResponse extends ToolResponse {
|
||||
output: {
|
||||
formId: string
|
||||
publishSettings: GoogleFormsPublishSettings
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// Watch Params & Responses
|
||||
// ============================================
|
||||
|
||||
export interface GoogleFormsCreateWatchParams {
|
||||
accessToken: string
|
||||
formId: string
|
||||
eventType: 'SCHEMA' | 'RESPONSES'
|
||||
topicName: string
|
||||
watchId?: string
|
||||
}
|
||||
|
||||
export interface GoogleFormsCreateWatchResponse extends ToolResponse {
|
||||
output: {
|
||||
id: string
|
||||
eventType: string
|
||||
topicName: string | null
|
||||
createTime: string | null
|
||||
expireTime: string | null
|
||||
state: string | null
|
||||
}
|
||||
}
|
||||
|
||||
export interface GoogleFormsListWatchesParams {
|
||||
accessToken: string
|
||||
formId: string
|
||||
}
|
||||
|
||||
export interface GoogleFormsListWatchesResponse extends ToolResponse {
|
||||
output: {
|
||||
watches: GoogleFormsWatch[]
|
||||
}
|
||||
}
|
||||
|
||||
export interface GoogleFormsDeleteWatchParams {
|
||||
accessToken: string
|
||||
formId: string
|
||||
watchId: string
|
||||
}
|
||||
|
||||
export interface GoogleFormsDeleteWatchResponse extends ToolResponse {
|
||||
output: {
|
||||
deleted: boolean
|
||||
}
|
||||
}
|
||||
|
||||
export interface GoogleFormsRenewWatchParams {
|
||||
accessToken: string
|
||||
formId: string
|
||||
watchId: string
|
||||
}
|
||||
|
||||
export interface GoogleFormsRenewWatchResponse extends ToolResponse {
|
||||
output: {
|
||||
id: string
|
||||
eventType: string | null
|
||||
expireTime: string | null
|
||||
state: string | null
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
|
||||
export const FORMS_API_BASE = 'https://forms.googleapis.com/v1'
|
||||
|
||||
const logger = createLogger('GoogleFormsUtils')
|
||||
|
||||
function isRecord(value: unknown): value is Record<string, unknown> {
|
||||
return value !== null && typeof value === 'object'
|
||||
}
|
||||
|
||||
export function getGoogleFormsErrorMessage(data: unknown, fallback: string): string {
|
||||
if (!isRecord(data)) return fallback
|
||||
|
||||
const { error } = data
|
||||
if (!isRecord(error)) return fallback
|
||||
|
||||
const { message } = error
|
||||
return typeof message === 'string' ? message : fallback
|
||||
}
|
||||
|
||||
export function buildListResponsesUrl(params: {
|
||||
formId: string
|
||||
pageSize?: number
|
||||
pageToken?: string
|
||||
filter?: string
|
||||
}): string {
|
||||
const { formId, pageSize, pageToken, filter } = params
|
||||
const url = new URL(`${FORMS_API_BASE}/forms/${encodeURIComponent(formId)}/responses`)
|
||||
if (pageSize && pageSize > 0) {
|
||||
const limited = Math.min(pageSize, 5000)
|
||||
url.searchParams.set('pageSize', String(limited))
|
||||
}
|
||||
if (pageToken) {
|
||||
url.searchParams.set('pageToken', pageToken)
|
||||
}
|
||||
if (filter) {
|
||||
url.searchParams.set('filter', filter)
|
||||
}
|
||||
const finalUrl = url.toString()
|
||||
logger.debug('Built Google Forms list responses URL', { finalUrl })
|
||||
return finalUrl
|
||||
}
|
||||
|
||||
export function buildGetResponseUrl(params: { formId: string; responseId: string }): string {
|
||||
const { formId, responseId } = params
|
||||
const finalUrl = `${FORMS_API_BASE}/forms/${encodeURIComponent(formId)}/responses/${encodeURIComponent(responseId)}`
|
||||
logger.debug('Built Google Forms get response URL', { finalUrl })
|
||||
return finalUrl
|
||||
}
|
||||
|
||||
export function buildGetFormUrl(formId: string): string {
|
||||
const finalUrl = `${FORMS_API_BASE}/forms/${encodeURIComponent(formId)}`
|
||||
logger.debug('Built Google Forms get form URL', { finalUrl })
|
||||
return finalUrl
|
||||
}
|
||||
|
||||
export function buildCreateFormUrl(unpublished?: boolean): string {
|
||||
const url = new URL(`${FORMS_API_BASE}/forms`)
|
||||
if (unpublished) {
|
||||
url.searchParams.set('unpublished', 'true')
|
||||
}
|
||||
const finalUrl = url.toString()
|
||||
logger.debug('Built Google Forms create form URL', { finalUrl })
|
||||
return finalUrl
|
||||
}
|
||||
|
||||
export function buildBatchUpdateUrl(formId: string): string {
|
||||
const finalUrl = `${FORMS_API_BASE}/forms/${encodeURIComponent(formId)}:batchUpdate`
|
||||
logger.debug('Built Google Forms batch update URL', { finalUrl })
|
||||
return finalUrl
|
||||
}
|
||||
|
||||
export function buildSetPublishSettingsUrl(formId: string): string {
|
||||
const finalUrl = `${FORMS_API_BASE}/forms/${encodeURIComponent(formId)}:setPublishSettings`
|
||||
logger.debug('Built Google Forms set publish settings URL', { finalUrl })
|
||||
return finalUrl
|
||||
}
|
||||
|
||||
export function buildListWatchesUrl(formId: string): string {
|
||||
const finalUrl = `${FORMS_API_BASE}/forms/${encodeURIComponent(formId)}/watches`
|
||||
logger.debug('Built Google Forms list watches URL', { finalUrl })
|
||||
return finalUrl
|
||||
}
|
||||
|
||||
export function buildCreateWatchUrl(formId: string): string {
|
||||
const finalUrl = `${FORMS_API_BASE}/forms/${encodeURIComponent(formId)}/watches`
|
||||
logger.debug('Built Google Forms create watch URL', { finalUrl })
|
||||
return finalUrl
|
||||
}
|
||||
|
||||
export function buildDeleteWatchUrl(formId: string, watchId: string): string {
|
||||
const finalUrl = `${FORMS_API_BASE}/forms/${encodeURIComponent(formId)}/watches/${encodeURIComponent(watchId)}`
|
||||
logger.debug('Built Google Forms delete watch URL', { finalUrl })
|
||||
return finalUrl
|
||||
}
|
||||
|
||||
export function buildRenewWatchUrl(formId: string, watchId: string): string {
|
||||
const finalUrl = `${FORMS_API_BASE}/forms/${encodeURIComponent(formId)}/watches/${encodeURIComponent(watchId)}:renew`
|
||||
logger.debug('Built Google Forms renew watch URL', { finalUrl })
|
||||
return finalUrl
|
||||
}
|
||||
Reference in New Issue
Block a user