runline 0.12.0 → 0.14.0

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.
@@ -22,7 +22,9 @@
22
22
  * the bare ID and normalize. The response always includes a
23
23
  * convenience `contactId` field stripped from `resourceName`.
24
24
  */
25
+ import * as t from "typebox";
25
26
  import { googleAccessToken } from "../../_shared/googleAuth.js";
27
+ import { Id, NonEmptyString, PositiveInteger, stringEnum, } from "../../_shared/googleSchemas.js";
26
28
  // ─── Fields ─────────────────────────────────────────────────────
27
29
  /**
28
30
  * Every `personFields` value supported by People API get/list calls.
@@ -279,6 +281,128 @@ function attachContactId(obj) {
279
281
  }
280
282
  // ─── Plugin ──────────────────────────────────────────────────────
281
283
  const SCOPES = ["https://www.googleapis.com/auth/contacts"];
284
+ const strictObject = (properties, options = {}) => t.Object(properties, { ...options, additionalProperties: false });
285
+ const optionalString = (description) => t.Optional(t.String(description ? { description } : {}));
286
+ const optionalBoolean = t.Optional(t.Boolean());
287
+ const personField = stringEnum([...ALL_PERSON_FIELDS, "*"]);
288
+ const personFields = t.Optional(t.Union([t.String(), t.Array(personField, { minItems: 1 })]));
289
+ const structuredDate = strictObject({
290
+ year: t.Optional(t.Integer({ minimum: 0 })),
291
+ month: t.Integer({ minimum: 1, maximum: 12 }),
292
+ day: t.Integer({ minimum: 1, maximum: 31 }),
293
+ });
294
+ const contactDate = t.Union([NonEmptyString, structuredDate]);
295
+ const typedValue = strictObject({
296
+ value: NonEmptyString,
297
+ type: optionalString(),
298
+ });
299
+ const relation = strictObject({
300
+ person: NonEmptyString,
301
+ type: optionalString(),
302
+ });
303
+ const contactEvent = strictObject({
304
+ date: contactDate,
305
+ type: optionalString(),
306
+ });
307
+ const userDefined = strictObject({ key: NonEmptyString, value: t.String() });
308
+ // These two People API resources are intentionally extensible. Known fields are
309
+ // typed, while additional provider fields remain available for forward
310
+ // compatibility and parity with the plugin's existing passthrough behavior.
311
+ const address = t.Object({
312
+ formattedValue: optionalString(),
313
+ type: optionalString(),
314
+ poBox: optionalString(),
315
+ streetAddress: optionalString(),
316
+ extendedAddress: optionalString(),
317
+ city: optionalString(),
318
+ region: optionalString(),
319
+ postalCode: optionalString(),
320
+ country: optionalString(),
321
+ countryCode: optionalString(),
322
+ }, { additionalProperties: true });
323
+ const organization = t.Object({
324
+ name: optionalString(),
325
+ title: optionalString(),
326
+ department: optionalString(),
327
+ type: optionalString(),
328
+ symbol: optionalString(),
329
+ domain: optionalString(),
330
+ location: optionalString(),
331
+ jobDescription: optionalString(),
332
+ phoneticName: optionalString(),
333
+ costCenter: optionalString(),
334
+ fullTimeEquivalentMillipercent: t.Optional(t.Integer({ minimum: 0, maximum: 100_000 })),
335
+ startDate: t.Optional(structuredDate),
336
+ endDate: t.Optional(structuredDate),
337
+ current: optionalBoolean,
338
+ }, { additionalProperties: true });
339
+ const clientData = strictObject({ key: NonEmptyString, value: t.String() });
340
+ const contactWritableFields = {
341
+ givenName: optionalString(),
342
+ familyName: optionalString(),
343
+ middleName: optionalString(),
344
+ honorificPrefix: optionalString(),
345
+ honorificSuffix: optionalString(),
346
+ phoneNumbers: t.Optional(t.Array(typedValue)),
347
+ emailAddresses: t.Optional(t.Array(typedValue)),
348
+ addresses: t.Optional(t.Array(address)),
349
+ organizations: t.Optional(t.Array(organization)),
350
+ relations: t.Optional(t.Array(relation)),
351
+ urls: t.Optional(t.Array(typedValue)),
352
+ events: t.Optional(t.Array(contactEvent)),
353
+ birthday: t.Optional(contactDate),
354
+ biography: optionalString(),
355
+ userDefined: t.Optional(t.Array(userDefined)),
356
+ groups: t.Optional(t.Array(NonEmptyString)),
357
+ };
358
+ const writableContactKeys = Object.keys(contactWritableFields);
359
+ const contactSchemas = {
360
+ create: strictObject(contactWritableFields),
361
+ get: strictObject({ contactId: Id, fields: personFields }),
362
+ list: strictObject({
363
+ query: optionalString(),
364
+ fields: personFields,
365
+ sortOrder: t.Optional(stringEnum([
366
+ "LAST_MODIFIED_ASCENDING",
367
+ "LAST_MODIFIED_DESCENDING",
368
+ "FIRST_NAME_ASCENDING",
369
+ "LAST_NAME_ASCENDING",
370
+ ])),
371
+ returnAll: optionalBoolean,
372
+ maxResults: t.Optional(PositiveInteger),
373
+ pageToken: optionalString(),
374
+ }),
375
+ update: strictObject({
376
+ contactId: Id,
377
+ etag: optionalString(),
378
+ fields: personFields,
379
+ ...contactWritableFields,
380
+ }, { anyOf: writableContactKeys.map((key) => ({ required: [key] })) }),
381
+ delete: strictObject({ contactId: Id }),
382
+ };
383
+ const groupSchemas = {
384
+ list: strictObject({
385
+ returnAll: optionalBoolean,
386
+ maxResults: t.Optional(PositiveInteger),
387
+ pageToken: optionalString(),
388
+ syncToken: optionalString(),
389
+ }),
390
+ get: strictObject({
391
+ groupId: Id,
392
+ maxMembers: t.Optional(t.Integer({ minimum: 0 })),
393
+ }),
394
+ create: strictObject({
395
+ name: NonEmptyString,
396
+ clientData: t.Optional(t.Array(clientData)),
397
+ }),
398
+ update: strictObject({
399
+ groupId: Id,
400
+ name: optionalString(),
401
+ clientData: t.Optional(t.Array(clientData)),
402
+ etag: optionalString(),
403
+ }, { anyOf: [{ required: ["name"] }, { required: ["clientData"] }] }),
404
+ delete: strictObject({ groupId: Id, deleteContacts: optionalBoolean }),
405
+ };
282
406
  export default function googleContacts(rl) {
283
407
  rl.setName("googleContacts");
284
408
  rl.setVersion("0.1.0");
@@ -328,60 +452,7 @@ export default function googleContacts(rl) {
328
452
  rl.registerAction("contact.create", {
329
453
  access: "write",
330
454
  description: "Create a new contact",
331
- inputSchema: {
332
- givenName: { type: "string", required: false },
333
- familyName: { type: "string", required: false },
334
- middleName: { type: "string", required: false },
335
- honorificPrefix: { type: "string", required: false },
336
- honorificSuffix: { type: "string", required: false },
337
- phoneNumbers: {
338
- type: "array",
339
- required: false,
340
- description: "[{value, type?}]",
341
- },
342
- emailAddresses: {
343
- type: "array",
344
- required: false,
345
- description: "[{value, type?}]",
346
- },
347
- addresses: {
348
- type: "array",
349
- required: false,
350
- description: "[{formattedValue?, streetAddress?, city?, region?, postalCode?, country?, type?}]",
351
- },
352
- organizations: {
353
- type: "array",
354
- required: false,
355
- description: "[{name, title?, department?, type?}]",
356
- },
357
- relations: {
358
- type: "array",
359
- required: false,
360
- description: "[{person, type?}]",
361
- },
362
- urls: { type: "array", required: false },
363
- events: {
364
- type: "array",
365
- required: false,
366
- description: "[{date, type?}] — date can be ISO string or {year, month, day}",
367
- },
368
- birthday: {
369
- type: "string",
370
- required: false,
371
- description: "ISO date string or {year, month, day}",
372
- },
373
- biography: { type: "string", required: false },
374
- userDefined: {
375
- type: "array",
376
- required: false,
377
- description: "[{key, value}] — custom key/value pairs",
378
- },
379
- groups: {
380
- type: "array",
381
- required: false,
382
- description: "Group IDs or full contactGroups/… resource names",
383
- },
384
- },
455
+ inputSchema: contactSchemas.create,
385
456
  async execute(input, ctx) {
386
457
  const p = (input ?? {});
387
458
  const { body } = buildPersonBody(p);
@@ -392,18 +463,7 @@ export default function googleContacts(rl) {
392
463
  rl.registerAction("contact.get", {
393
464
  access: "read",
394
465
  description: "Get a contact by ID",
395
- inputSchema: {
396
- contactId: {
397
- type: "string",
398
- required: true,
399
- description: "Bare ID or full people/… resource name",
400
- },
401
- fields: {
402
- type: "string",
403
- required: false,
404
- description: "'*' (all), comma-separated string, or array of People-API field names (default: names, emailAddresses, phoneNumbers)",
405
- },
406
- },
466
+ inputSchema: contactSchemas.get,
407
467
  async execute(input, ctx) {
408
468
  const p = (input ?? {});
409
469
  const resource = normalizeContactResource(p.contactId);
@@ -414,22 +474,7 @@ export default function googleContacts(rl) {
414
474
  rl.registerAction("contact.list", {
415
475
  access: "read",
416
476
  description: "List contacts (people/me/connections) or search them. When `query` is set, hits people:searchContacts; otherwise returns the user's connections.",
417
- inputSchema: {
418
- query: {
419
- type: "string",
420
- required: false,
421
- description: "If set, uses people:searchContacts instead of connections.list",
422
- },
423
- fields: { type: "string", required: false },
424
- sortOrder: {
425
- type: "string",
426
- required: false,
427
- description: "LAST_MODIFIED_ASCENDING | LAST_MODIFIED_DESCENDING | FIRST_NAME_ASCENDING | LAST_NAME_ASCENDING (connections.list only)",
428
- },
429
- returnAll: { type: "boolean", required: false },
430
- maxResults: { type: "number", required: false },
431
- pageToken: { type: "string", required: false },
432
- },
477
+ inputSchema: contactSchemas.list,
433
478
  async execute(input, ctx) {
434
479
  const p = (input ?? {});
435
480
  const useSearch = typeof p.query === "string" && p.query.length > 0;
@@ -484,36 +529,7 @@ export default function googleContacts(rl) {
484
529
  rl.registerAction("contact.update", {
485
530
  access: "write",
486
531
  description: "Update a contact. Only supplied fields are sent; etag is resolved automatically if not provided.",
487
- inputSchema: {
488
- contactId: { type: "string", required: true },
489
- etag: {
490
- type: "string",
491
- required: false,
492
- description: "Optimistic-concurrency tag. If omitted, the plugin fetches the latest etag first.",
493
- },
494
- fields: {
495
- type: "string",
496
- required: false,
497
- description: "personFields projection on the response (same semantics as contact.get)",
498
- },
499
- // Writable fields
500
- givenName: { type: "string", required: false },
501
- familyName: { type: "string", required: false },
502
- middleName: { type: "string", required: false },
503
- honorificPrefix: { type: "string", required: false },
504
- honorificSuffix: { type: "string", required: false },
505
- phoneNumbers: { type: "array", required: false },
506
- emailAddresses: { type: "array", required: false },
507
- addresses: { type: "array", required: false },
508
- organizations: { type: "array", required: false },
509
- relations: { type: "array", required: false },
510
- urls: { type: "array", required: false },
511
- events: { type: "array", required: false },
512
- birthday: { type: "string", required: false },
513
- biography: { type: "string", required: false },
514
- userDefined: { type: "array", required: false },
515
- groups: { type: "array", required: false },
516
- },
532
+ inputSchema: contactSchemas.update,
517
533
  async execute(input, ctx) {
518
534
  const p = (input ?? {});
519
535
  const resource = normalizeContactResource(p.contactId);
@@ -539,7 +555,7 @@ export default function googleContacts(rl) {
539
555
  rl.registerAction("contact.delete", {
540
556
  access: "write",
541
557
  description: "Delete a contact",
542
- inputSchema: { contactId: { type: "string", required: true } },
558
+ inputSchema: contactSchemas.delete,
543
559
  async execute(input, ctx) {
544
560
  const p = (input ?? {});
545
561
  const resource = normalizeContactResource(p.contactId);
@@ -551,12 +567,7 @@ export default function googleContacts(rl) {
551
567
  rl.registerAction("group.list", {
552
568
  access: "read",
553
569
  description: "List contact groups (including system groups like 'myContacts' and 'starred')",
554
- inputSchema: {
555
- returnAll: { type: "boolean", required: false },
556
- maxResults: { type: "number", required: false },
557
- pageToken: { type: "string", required: false },
558
- syncToken: { type: "string", required: false },
559
- },
570
+ inputSchema: groupSchemas.list,
560
571
  async execute(input, ctx) {
561
572
  const p = (input ?? {});
562
573
  const qs = {};
@@ -575,14 +586,7 @@ export default function googleContacts(rl) {
575
586
  rl.registerAction("group.get", {
576
587
  access: "read",
577
588
  description: "Get a contact group by ID",
578
- inputSchema: {
579
- groupId: { type: "string", required: true },
580
- maxMembers: {
581
- type: "number",
582
- required: false,
583
- description: "Include up to N member resourceNames in the response",
584
- },
585
- },
589
+ inputSchema: groupSchemas.get,
586
590
  async execute(input, ctx) {
587
591
  const p = (input ?? {});
588
592
  const resource = normalizeGroupResource(p.groupId);
@@ -595,14 +599,7 @@ export default function googleContacts(rl) {
595
599
  rl.registerAction("group.create", {
596
600
  access: "write",
597
601
  description: "Create a contact group",
598
- inputSchema: {
599
- name: { type: "string", required: true },
600
- clientData: {
601
- type: "array",
602
- required: false,
603
- description: "[{key, value}] — app-private metadata",
604
- },
605
- },
602
+ inputSchema: groupSchemas.create,
606
603
  async execute(input, ctx) {
607
604
  const p = (input ?? {});
608
605
  const body = {
@@ -617,12 +614,7 @@ export default function googleContacts(rl) {
617
614
  rl.registerAction("group.update", {
618
615
  access: "write",
619
616
  description: "Update a contact group. Pass a fresh `etag` or let the plugin resolve it automatically.",
620
- inputSchema: {
621
- groupId: { type: "string", required: true },
622
- name: { type: "string", required: false },
623
- clientData: { type: "array", required: false },
624
- etag: { type: "string", required: false },
625
- },
617
+ inputSchema: groupSchemas.update,
626
618
  async execute(input, ctx) {
627
619
  const p = (input ?? {});
628
620
  const resource = normalizeGroupResource(p.groupId);
@@ -653,10 +645,7 @@ export default function googleContacts(rl) {
653
645
  rl.registerAction("group.delete", {
654
646
  access: "write",
655
647
  description: "Delete a contact group. Pass `deleteContacts=true` to also delete every contact in the group.",
656
- inputSchema: {
657
- groupId: { type: "string", required: true },
658
- deleteContacts: { type: "boolean", required: false },
659
- },
648
+ inputSchema: groupSchemas.delete,
660
649
  async execute(input, ctx) {
661
650
  const p = (input ?? {});
662
651
  const resource = normalizeGroupResource(p.groupId);
@@ -1,16 +1,16 @@
1
- import { DRIVE_BASE, docsRequest, extractDocumentId, flattenBodyText, } from "./shared.js";
1
+ import * as t from "typebox";
2
+ import { DocumentInput, DRIVE_BASE, docsRequest, extractDocumentId, flattenBodyText, RawGoogleObject, STRICT_OBJECT, WriteControl, } from "./shared.js";
2
3
  export function registerDocumentsActions(rl) {
3
4
  rl.registerAction("document.create", {
4
5
  access: "write",
5
6
  description: "Create a new Google Doc, optionally in a specific Drive folder (goes through the Drive API; needs drive.file scope).",
6
- inputSchema: {
7
- title: { type: "string", required: true },
8
- folderId: {
9
- type: "string",
10
- required: false,
7
+ inputSchema: t.Object({
8
+ title: t.String({ minLength: 1 }),
9
+ folderId: t.Optional(t.String({
10
+ minLength: 1,
11
11
  description: "Parent folder in Drive. Omit to place in My Drive root.",
12
- },
13
- },
12
+ })),
13
+ }, STRICT_OBJECT),
14
14
  async execute(input, ctx) {
15
15
  const p = (input ?? {});
16
16
  const body = {
@@ -26,9 +26,7 @@ export function registerDocumentsActions(rl) {
26
26
  rl.registerAction("document.createBlank", {
27
27
  access: "write",
28
28
  description: "Create a blank Google Doc through the native Docs API. Use document.create when you need Drive folder placement.",
29
- inputSchema: {
30
- title: { type: "string", required: true },
31
- },
29
+ inputSchema: t.Object({ title: t.String({ minLength: 1 }) }, STRICT_OBJECT),
32
30
  async execute(input, ctx) {
33
31
  const p = (input ?? {});
34
32
  return docsRequest(ctx, "POST", "/documents", { title: p.title });
@@ -37,24 +35,19 @@ export function registerDocumentsActions(rl) {
37
35
  rl.registerAction("document.get", {
38
36
  access: "read",
39
37
  description: "Get a document. Accepts a bare ID or a docs.google.com URL. `simple=true` collapses the body to plain text.",
40
- inputSchema: {
41
- document: {
42
- type: "string",
43
- required: true,
44
- description: "Document ID or URL",
45
- },
46
- simple: { type: "boolean", required: false },
47
- suggestionsViewMode: {
48
- type: "string",
49
- required: false,
50
- description: "DEFAULT_FOR_CURRENT_ACCESS | SUGGESTIONS_INLINE | PREVIEW_SUGGESTIONS_ACCEPTED | PREVIEW_WITHOUT_SUGGESTIONS",
51
- },
52
- includeTabsContent: {
53
- type: "boolean",
54
- required: false,
55
- description: "Return content for all tabs in document.tabs instead of only first-tab legacy fields.",
56
- },
57
- },
38
+ inputSchema: t.Object({
39
+ ...DocumentInput,
40
+ simple: t.Optional(t.Boolean()),
41
+ suggestionsViewMode: t.Optional(t.Union([
42
+ t.Literal("DEFAULT_FOR_CURRENT_ACCESS"),
43
+ t.Literal("SUGGESTIONS_INLINE"),
44
+ t.Literal("PREVIEW_SUGGESTIONS_ACCEPTED"),
45
+ t.Literal("PREVIEW_WITHOUT_SUGGESTIONS"),
46
+ ])),
47
+ includeTabsContent: t.Optional(t.Boolean({
48
+ description: "Return content for all tabs instead of only first-tab legacy fields.",
49
+ })),
50
+ }, STRICT_OBJECT),
58
51
  async execute(input, ctx) {
59
52
  const p = (input ?? {});
60
53
  const documentId = extractDocumentId(p.document);
@@ -72,15 +65,11 @@ export function registerDocumentsActions(rl) {
72
65
  rl.registerAction("document.batchUpdate", {
73
66
  access: "write",
74
67
  description: "Raw passthrough to documents.batchUpdate — pass a full `requests` array for atomic multi-edit operations.",
75
- inputSchema: {
76
- document: { type: "string", required: true },
77
- requests: { type: "array", required: true },
78
- writeControl: {
79
- type: "object",
80
- required: false,
81
- description: "{requiredRevisionId} | {targetRevisionId}",
82
- },
83
- },
68
+ inputSchema: t.Object({
69
+ ...DocumentInput,
70
+ requests: t.Array(RawGoogleObject, { minItems: 1 }),
71
+ writeControl: t.Optional(WriteControl),
72
+ }, STRICT_OBJECT),
84
73
  async execute(input, ctx) {
85
74
  const p = (input ?? {});
86
75
  const documentId = extractDocumentId(p.document);
@@ -1,40 +1,50 @@
1
- import { compact, extractDocumentId, runBatchUpdate } from "./shared.js";
1
+ import * as t from "typebox";
2
+ import { compact, DocumentInput, extractDocumentId, PositivePoints, RangeInput, RawGoogleObject, runBatchUpdate, STRICT_OBJECT, } from "./shared.js";
2
3
  export function registerFormattingActions(rl) {
3
4
  rl.registerAction("document.updateParagraphStyle", {
4
5
  access: "write",
5
6
  description: "Apply paragraph styling (alignment, named style, indents, spacing, direction) to the paragraphs intersecting the range.",
6
- inputSchema: {
7
- document: { type: "string", required: true },
8
- startIndex: { type: "number", required: true },
9
- endIndex: { type: "number", required: true },
10
- alignment: {
11
- type: "string",
12
- required: false,
13
- description: "START | CENTER | END | JUSTIFIED",
14
- },
15
- namedStyleType: {
16
- type: "string",
17
- required: false,
18
- description: "NORMAL_TEXT | TITLE | SUBTITLE | HEADING_1 .. HEADING_6",
19
- },
20
- direction: {
21
- type: "string",
22
- required: false,
23
- description: "LEFT_TO_RIGHT | RIGHT_TO_LEFT",
24
- },
25
- indentFirstLinePt: { type: "number", required: false },
26
- indentStartPt: { type: "number", required: false },
27
- indentEndPt: { type: "number", required: false },
28
- spaceAbovePt: { type: "number", required: false },
29
- spaceBelowPt: { type: "number", required: false },
30
- lineSpacing: {
31
- type: "number",
32
- required: false,
33
- description: "Percentage; 100 = single, 150 = 1.5x.",
34
- },
35
- segmentId: { type: "string", required: false },
36
- tabId: { type: "string", required: false },
37
- },
7
+ inputSchema: t.Object({
8
+ ...DocumentInput,
9
+ ...RangeInput,
10
+ alignment: t.Optional(t.Union([
11
+ t.Literal("START"),
12
+ t.Literal("CENTER"),
13
+ t.Literal("END"),
14
+ t.Literal("JUSTIFIED"),
15
+ ])),
16
+ namedStyleType: t.Optional(t.Union([
17
+ t.Literal("NORMAL_TEXT"),
18
+ t.Literal("TITLE"),
19
+ t.Literal("SUBTITLE"),
20
+ t.Literal("HEADING_1"),
21
+ t.Literal("HEADING_2"),
22
+ t.Literal("HEADING_3"),
23
+ t.Literal("HEADING_4"),
24
+ t.Literal("HEADING_5"),
25
+ t.Literal("HEADING_6"),
26
+ ])),
27
+ direction: t.Optional(t.Union([t.Literal("LEFT_TO_RIGHT"), t.Literal("RIGHT_TO_LEFT")])),
28
+ indentFirstLinePt: t.Optional(PositivePoints),
29
+ indentStartPt: t.Optional(PositivePoints),
30
+ indentEndPt: t.Optional(PositivePoints),
31
+ spaceAbovePt: t.Optional(PositivePoints),
32
+ spaceBelowPt: t.Optional(PositivePoints),
33
+ lineSpacing: t.Optional(t.Number({ minimum: 0 })),
34
+ }, {
35
+ ...STRICT_OBJECT,
36
+ anyOf: [
37
+ { required: ["alignment"] },
38
+ { required: ["namedStyleType"] },
39
+ { required: ["direction"] },
40
+ { required: ["indentFirstLinePt"] },
41
+ { required: ["indentStartPt"] },
42
+ { required: ["indentEndPt"] },
43
+ { required: ["spaceAbovePt"] },
44
+ { required: ["spaceBelowPt"] },
45
+ { required: ["lineSpacing"] },
46
+ ],
47
+ }),
38
48
  async execute(input, ctx) {
39
49
  const p = (input ?? {});
40
50
  const documentId = extractDocumentId(p.document);
@@ -99,20 +109,12 @@ export function registerFormattingActions(rl) {
99
109
  rl.registerAction("document.updateNamedStyle", {
100
110
  access: "write",
101
111
  description: "Update a named style such as NORMAL_TEXT, TITLE, or HEADING_1 using a Docs API NamedStyle object.",
102
- inputSchema: {
103
- document: { type: "string", required: true },
104
- namedStyle: {
105
- type: "object",
106
- required: true,
107
- description: "Docs API NamedStyle object. Must include namedStyleType.",
108
- },
109
- fields: {
110
- type: "string",
111
- required: true,
112
- description: "Field mask such as textStyle.bold or paragraphStyle.alignment.",
113
- },
114
- tabId: { type: "string", required: false },
115
- },
112
+ inputSchema: t.Object({
113
+ ...DocumentInput,
114
+ namedStyle: RawGoogleObject,
115
+ fields: t.String({ minLength: 1 }),
116
+ tabId: t.Optional(t.String({ minLength: 1 })),
117
+ }, STRICT_OBJECT),
116
118
  async execute(input, ctx) {
117
119
  const p = (input ?? {});
118
120
  const documentId = extractDocumentId(p.document);
@@ -1,22 +1,21 @@
1
- import { buildLocation, extractDocumentId, runBatchUpdate } from "./shared.js";
1
+ import * as t from "typebox";
2
+ import { buildLocation, DocumentInput, extractDocumentId, LOCATION_REQUIREMENT, LocationInput, PositivePoints, runBatchUpdate, STRICT_OBJECT, } from "./shared.js";
3
+ const PublicImageUri = t.String({
4
+ minLength: 1,
5
+ pattern: "^https?://",
6
+ description: "Publicly fetchable HTTP(S) image URI",
7
+ });
2
8
  export function registerImagesActions(rl) {
3
9
  rl.registerAction("document.insertInlineImage", {
4
10
  access: "write",
5
11
  description: "Insert an inline image at the given location. `uri` must point to a publicly fetchable image.",
6
- inputSchema: {
7
- document: { type: "string", required: true },
8
- locationKind: {
9
- type: "string",
10
- required: false,
11
- description: "location (default; requires index) | endOfSegmentLocation",
12
- },
13
- index: { type: "number", required: false },
14
- uri: { type: "string", required: true },
15
- widthPt: { type: "number", required: false },
16
- heightPt: { type: "number", required: false },
17
- segmentId: { type: "string", required: false },
18
- tabId: { type: "string", required: false },
19
- },
12
+ inputSchema: t.Object({
13
+ ...DocumentInput,
14
+ ...LocationInput,
15
+ uri: PublicImageUri,
16
+ widthPt: t.Optional(PositivePoints),
17
+ heightPt: t.Optional(PositivePoints),
18
+ }, { ...STRICT_OBJECT, anyOf: LOCATION_REQUIREMENT }),
20
19
  async execute(input, ctx) {
21
20
  const p = (input ?? {});
22
21
  const documentId = extractDocumentId(p.document);
@@ -39,17 +38,13 @@ export function registerImagesActions(rl) {
39
38
  rl.registerAction("document.replaceImage", {
40
39
  access: "write",
41
40
  description: "Replace an existing image (identified by its inline-object id) with a new image from a publicly fetchable URI.",
42
- inputSchema: {
43
- document: { type: "string", required: true },
44
- imageObjectId: { type: "string", required: true },
45
- uri: { type: "string", required: true },
46
- imageReplaceMethod: {
47
- type: "string",
48
- required: false,
49
- description: "CENTER_CROP (default) | (others as Docs API adds them)",
50
- },
51
- tabId: { type: "string", required: false },
52
- },
41
+ inputSchema: t.Object({
42
+ ...DocumentInput,
43
+ imageObjectId: t.String({ minLength: 1 }),
44
+ uri: PublicImageUri,
45
+ imageReplaceMethod: t.Optional(t.Literal("CENTER_CROP")),
46
+ tabId: t.Optional(t.String({ minLength: 1 })),
47
+ }, STRICT_OBJECT),
53
48
  async execute(input, ctx) {
54
49
  const p = (input ?? {});
55
50
  const documentId = extractDocumentId(p.document);