@zackbart/connecta 0.41.0 → 0.42.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.
Files changed (148) hide show
  1. package/CHANGELOG.md +61 -0
  2. package/bin/connecta.mjs +26 -17
  3. package/bin/meta-tool-names.d.mts +3 -1
  4. package/bin/meta-tool-names.mjs +4 -1
  5. package/bin/version.mjs +1 -1
  6. package/dist/activity.d.ts +2 -1
  7. package/dist/activity.js +5 -5
  8. package/dist/advertised-schema.js +15 -0
  9. package/dist/agent-routes.js +31 -5
  10. package/dist/auth/downstream-oauth/refresh-coordinator.js +4 -1
  11. package/dist/auth/downstream-oauth.js +3 -0
  12. package/dist/auth/static-oauth.js +26 -18
  13. package/dist/auth-elicitation.js +118 -61
  14. package/dist/authorization-handoff.js +36 -3
  15. package/dist/catalog-cache.js +292 -24
  16. package/dist/catalog-freshness.js +12 -0
  17. package/dist/catalog-service.js +373 -70
  18. package/dist/catalog.js +147 -58
  19. package/dist/config-defaults.d.ts +2 -1
  20. package/dist/config-defaults.js +4 -1
  21. package/dist/config.d.ts +43 -17
  22. package/dist/config.js +26 -10
  23. package/dist/connectors/remote-mcp/catalog.js +52 -28
  24. package/dist/connectors/remote-mcp/connection.js +62 -5
  25. package/dist/connectors/remote-mcp.js +10 -1
  26. package/dist/describe-config.d.ts +3 -2
  27. package/dist/discovery-health.js +26 -11
  28. package/dist/errors.d.ts +52 -3
  29. package/dist/errors.js +86 -3
  30. package/dist/execute.js +476 -200
  31. package/dist/executor-admission.js +2 -0
  32. package/dist/executor-contract.js +6 -0
  33. package/dist/executor-result.js +2 -2
  34. package/dist/executors/quickjs-child.js +13 -4
  35. package/dist/executors/quickjs-runtime.d.ts +2 -0
  36. package/dist/executors/quickjs-runtime.js +36 -19
  37. package/dist/executors/quickjs.js +23 -6
  38. package/dist/guest-functions.js +5 -4
  39. package/dist/guest-runtime.js +37 -1
  40. package/dist/guest-types.d.ts +18 -0
  41. package/dist/http/client.js +7 -2
  42. package/dist/http/errors.js +9 -4
  43. package/dist/http/fields.js +17 -5
  44. package/dist/http/hatch.js +9 -3
  45. package/dist/http/paging.js +1 -1
  46. package/dist/http/provider.js +2 -6
  47. package/dist/http/vendor.js +6 -1
  48. package/dist/index.d.ts +1 -1
  49. package/dist/index.js +5 -0
  50. package/dist/invocation.js +98 -34
  51. package/dist/meta-output.js +24 -18
  52. package/dist/meta-tool-names.d.ts +1 -1
  53. package/dist/meta-tool-names.js +1 -1
  54. package/dist/meta-tools.js +259 -60
  55. package/dist/native-recovery.js +4 -1
  56. package/dist/native-tools.js +166 -51
  57. package/dist/node.js +3 -1
  58. package/dist/oauth-handoff.js +53 -43
  59. package/dist/operation-activity.js +2 -1
  60. package/dist/operation-vocabulary.js +6 -0
  61. package/dist/operator-record.d.ts +1 -1
  62. package/dist/operator-record.js +1 -0
  63. package/dist/operator-ui/generated.js +2 -2
  64. package/dist/program-result.js +2 -0
  65. package/dist/program-source.js +1 -1
  66. package/dist/program-writes.js +112 -19
  67. package/dist/provider.d.ts +0 -13
  68. package/dist/provider.js +0 -3
  69. package/dist/providers/_shared/google/kit.js +2 -2
  70. package/dist/providers/breeze/api.js +2 -2
  71. package/dist/providers/breeze/skill.generated.js +1 -1
  72. package/dist/providers/ccb/api.js +2 -2
  73. package/dist/providers/ccb/skill.generated.js +1 -1
  74. package/dist/providers/cloudflare/api.js +2 -2
  75. package/dist/providers/cloudflare/index.js +2 -0
  76. package/dist/providers/cloudflare/skill.generated.js +1 -1
  77. package/dist/providers/docs/skill.generated.js +1 -1
  78. package/dist/providers/drive/skill.generated.js +1 -1
  79. package/dist/providers/forms/index.js +1 -0
  80. package/dist/providers/forms/skill.generated.js +1 -1
  81. package/dist/providers/github/api.js +98 -6
  82. package/dist/providers/github/index.js +7 -2
  83. package/dist/providers/github/lib/auth.d.ts +3 -1
  84. package/dist/providers/github/lib/auth.js +43 -12
  85. package/dist/providers/github/lib/discovery.js +11 -1
  86. package/dist/providers/github/lib/operations.js +8 -0
  87. package/dist/providers/github/safety.js +39 -0
  88. package/dist/providers/github/skill.generated.js +1 -1
  89. package/dist/providers/github/tools.js +12 -5
  90. package/dist/providers/gmail/index.js +1 -0
  91. package/dist/providers/gmail/skill.generated.js +1 -1
  92. package/dist/providers/granola/skill.generated.js +1 -1
  93. package/dist/providers/overflow/index.js +2 -0
  94. package/dist/providers/overflow/skill.generated.js +1 -1
  95. package/dist/providers/planning-center/index.js +11 -5
  96. package/dist/providers/planning-center/lib/constants.d.ts +1 -1
  97. package/dist/providers/planning-center/skill.generated.js +1 -1
  98. package/dist/providers/quickbooks/index.js +2 -5
  99. package/dist/providers/quickbooks/skill.generated.js +1 -1
  100. package/dist/providers/sheets/index.js +1 -0
  101. package/dist/providers/sheets/skill.generated.js +1 -1
  102. package/dist/providers/slides/index.js +2 -2
  103. package/dist/providers/slides/skill.generated.js +1 -1
  104. package/dist/providers/stripe/skill.generated.js +1 -1
  105. package/dist/providers/tithely/index.js +1 -0
  106. package/dist/providers/tithely/skill.generated.js +1 -1
  107. package/dist/providers/vercel/api.js +2 -2
  108. package/dist/providers/vercel/skill.generated.js +1 -1
  109. package/dist/registry.d.ts +36 -4
  110. package/dist/registry.js +127 -27
  111. package/dist/result-preview.js +210 -0
  112. package/dist/result-seal.js +168 -0
  113. package/dist/route-names.js +13 -4
  114. package/dist/routes/connect.js +21 -13
  115. package/dist/routes/mcp.js +55 -26
  116. package/dist/routes/shared.d.ts +6 -0
  117. package/dist/runtime/call.js +15 -10
  118. package/dist/runtime/request-scope.js +36 -1
  119. package/dist/runtime/run.js +4 -1
  120. package/dist/sent-secrets.d.ts +20 -0
  121. package/dist/sent-secrets.js +49 -1
  122. package/dist/skills.js +100 -33
  123. package/dist/storage/keys.js +9 -6
  124. package/dist/storage/sql-native.js +1 -1
  125. package/dist/tool-safety.d.ts +33 -0
  126. package/dist/tool-safety.js +51 -15
  127. package/dist/types.d.ts +31 -0
  128. package/dist/usage-guide.js +14 -13
  129. package/dist/validate.js +29 -10
  130. package/dist/version.d.ts +1 -1
  131. package/dist/version.js +1 -1
  132. package/dist/worker.js +28 -8
  133. package/documentation/architecture.md +104 -42
  134. package/documentation/auth.md +46 -19
  135. package/documentation/code-mode.md +378 -116
  136. package/documentation/deploying.md +10 -9
  137. package/documentation/hosted-mcp-migration.md +9 -3
  138. package/documentation/integrating.md +27 -5
  139. package/documentation/meta-tools.md +655 -153
  140. package/documentation/operating.md +5 -3
  141. package/documentation/providers.md +18 -6
  142. package/examples/worker/README.md +3 -3
  143. package/guides/how-it-works.md +2 -1
  144. package/guides/safety-and-trust.md +1 -3
  145. package/package.json +1 -1
  146. package/templates/node/AGENTS.md +3 -3
  147. package/templates/node/README.md +1 -1
  148. package/templates/node/package.json +1 -1
@@ -3,3 +3,5 @@ export const PROGRAM_RESULT_INLINE_BYTES = 1024 * 1024;
3
3
  /** Leave room for the bridge envelope and six-character JSON escapes per byte. */
4
4
  export const PROGRAM_RESULT_PAGE_BYTES = 32 * 1024;
5
5
  export const PROGRAM_RESULT_WIRE_BYTES = 240 * 1024;
6
+ /** What `connecta.result(id, { all: true })` delivers unless the executor says less. */
7
+ export const PROGRAM_RESULT_READ_BYTES = 1.5 * 1024 * 1024;
@@ -6,7 +6,7 @@ const FENCE = /^```[\w-]*\s*\n([\s\S]*?)\n?```$/;
6
6
  /** Intake accepts one JavaScript fence, optionally surrounded by prose. */
7
7
  const PROGRAM_FENCE = /^```(?:js|javascript)?[ \t]*\r?\n([\s\S]*?)\r?\n```[ \t]*$/gm;
8
8
  /** Leading whitespace and comments, which may precede the arrow expression. */
9
- const LEADING_TRIVIA = /^(?:\s+|\/\/[^\n]*|\/\*[\s\S]*?\*\/)+/;
9
+ export const LEADING_TRIVIA = /^(?:\s+|\/\/[^\n]*|\/\*[\s\S]*?\*\/)+/;
10
10
  /** One character of an identifier, keyword, or number; `\\` for escapes. */
11
11
  const WORD = /[\w$\\\u0080-\uffff]/;
12
12
  /**
@@ -3,9 +3,35 @@ import { toolError } from "./tool-error.js";
3
3
  // Await each dispatched write once; never conceal an unknown outcome.
4
4
  import { CONFIG_DEFAULTS } from "./config-defaults.js";
5
5
  import { Effect } from "effect";
6
+ import { clipText, MAX_TOOL_ERROR_BYTES } from "./errors.js";
6
7
  import { classifyWriteOutcome } from "./write-outcome.js";
7
8
  /** Writes one program may send (`execute.maxWrites`), unless configured. */
8
9
  export const DEFAULT_MAX_WRITES = CONFIG_DEFAULTS.execute.maxWrites;
10
+ /** Unknown writes one run retains and reports. */
11
+ const MAX_REPORTED_WRITES = 10;
12
+ /** UTF-16 code units per write, so its UTF-8 size can reach three times this. */
13
+ const RETAINED_UNITS = 4 * MAX_TOOL_ERROR_BYTES;
14
+ /** Serialized bytes of the whole report: two full messages, or more shorter ones. */
15
+ const MAX_REPORT_BYTES = 2 * MAX_TOOL_ERROR_BYTES;
16
+ /** Room kept for what joins the result after the report: counters and a quoted address. */
17
+ const RESULT_RESERVE = 256;
18
+ const CHECK_UNKNOWN_WRITE = "Read its target to see whether it applied before anything depends on it.";
19
+ const UNKNOWN_WRITE_ARGS = "Echoed arguments may be partial or absent; use your original arguments for further calls.";
20
+ const NO_PROGRAM_RERUN = "Do not rerun this program: writes it already sent would repeat. If a tool's words call for a further call, send only that operation, as a new call.";
21
+ const encoder = new TextEncoder();
22
+ const serializedBytes = (value) => encoder.encode(JSON.stringify(value)).length;
23
+ /** `text` clipped so its JSON string fits `room` bytes, or nothing when not even the marker does. */
24
+ function fitted(text, room) {
25
+ let budget = room;
26
+ let clipped = clipText(text, budget);
27
+ for (let size = serializedBytes(clipped); size > room; size = serializedBytes(clipped)) {
28
+ if (budget <= 0)
29
+ return undefined;
30
+ budget = Math.floor((budget * room) / size) - 1;
31
+ clipped = clipText(text, Math.max(0, budget));
32
+ }
33
+ return clipped;
34
+ }
9
35
  /** What a dispatched write's outcome says about whether it landed. */
10
36
  export function writeStateOf(outcome) {
11
37
  const state = classifyWriteOutcome(outcome.ok
@@ -19,22 +45,33 @@ export function writeStateOf(outcome) {
19
45
  if (state !== "unknown" || outcome.ok)
20
46
  return state;
21
47
  const { error } = outcome;
48
+ const text = outcome.failureText ?? error.message;
22
49
  const handoff = (error.code === "auth_required" || error.code === "downstream_oauth_required") &&
23
50
  error.nextAction &&
24
- "tool" in error.nextAction &&
25
- error.nextAction.tool === "authorize_connector"
51
+ error.connector !== undefined
26
52
  ? {
27
53
  code: error.code,
28
- ...(error.connector !== undefined ? { connector: error.connector } : {}),
54
+ connector: error.connector,
29
55
  ...(error.recovery !== undefined ? { recovery: error.recovery } : {}),
30
- nextAction: error.nextAction,
56
+ // This terminal result carries no handoff fields, so it never holds a link:
57
+ // the agent fetches one, whatever the call's own error was given.
58
+ nextAction: {
59
+ tool: "authorize_connector",
60
+ arguments: { connector: error.connector },
61
+ operatorHandoff: "Give the URL and instructions it returns to the operator.",
62
+ },
31
63
  ...(error.retry !== undefined ? { retry: error.retry } : {}),
32
64
  }
33
65
  : undefined;
34
- if (!error.uncertainCall && !handoff)
35
- return state;
36
66
  return {
37
67
  state,
68
+ failure: {
69
+ ...(outcome.resolved ? { address: `${outcome.resolved.connector.id}.${outcome.resolved.toolName}` } : {}),
70
+ code: error.code,
71
+ ...(error.toolAnswered ? { toolAnswered: true } : {}),
72
+ text: text.slice(0, RETAINED_UNITS),
73
+ cut: text.length > RETAINED_UNITS,
74
+ },
38
75
  ...(error.uncertainCall ? { uncertainCall: error.uncertainCall } : {}),
39
76
  ...(error.uncertainCall && error.details?.stage !== undefined
40
77
  ? {
@@ -61,6 +98,8 @@ export class ProgramWrites {
61
98
  closed = false;
62
99
  inFlight = new Set();
63
100
  states = [];
101
+ unknownWrites = [];
102
+ unknownWritesTruncated = false;
64
103
  uncertainCalls = [];
65
104
  uncertainCallsTruncated = false;
66
105
  deadlines = [];
@@ -90,6 +129,10 @@ export class ProgramWrites {
90
129
  recorded = true;
91
130
  const state = typeof completion === "string" ? completion : completion.state;
92
131
  if (typeof completion !== "string") {
132
+ if (this.unknownWrites.length < MAX_REPORTED_WRITES)
133
+ this.unknownWrites.push(completion.failure);
134
+ else
135
+ this.unknownWritesTruncated = true;
93
136
  if (completion.uncertainCall) {
94
137
  if (this.uncertainCalls.length < 10)
95
138
  this.uncertainCalls.push(completion.uncertainCall);
@@ -105,6 +148,10 @@ export class ProgramWrites {
105
148
  done();
106
149
  };
107
150
  }
151
+ /** Whether this run dispatched any write, whatever became of it. */
152
+ get sent() {
153
+ return this.states.length > 0 || this.inFlight.size > 0;
154
+ }
108
155
  /** Wait for dispatched writes, each bounded by its host-call deadline. */
109
156
  drain() {
110
157
  return Effect.promise(async () => {
@@ -112,7 +159,29 @@ export class ProgramWrites {
112
159
  await Promise.all(this.inFlight);
113
160
  });
114
161
  }
115
- finish(result) {
162
+ /**
163
+ * What each unknown write said, checked against the run's final credentials and only
164
+ * then cut, so a credential a later call registered matches whole. A retained text
165
+ * was itself cut, so it also loses any unfinished tail. `room` bounds the serialized report.
166
+ */
167
+ report(secrets, room) {
168
+ const entries = [];
169
+ let truncated = this.unknownWritesTruncated;
170
+ let left = room;
171
+ for (const { text, cut, ...facts } of this.unknownWrites) {
172
+ left -= serializedBytes(facts) + 12;
173
+ if (left < 0)
174
+ return { entries, truncated: true };
175
+ const message = fitted(cut ? secrets.redactOpenTail(text) : secrets.text(text), Math.min(left, MAX_TOOL_ERROR_BYTES));
176
+ if (message === undefined)
177
+ truncated = true;
178
+ else
179
+ left -= serializedBytes(message);
180
+ entries.push({ ...facts, ...(message === undefined ? {} : { message }) });
181
+ }
182
+ return { entries, truncated };
183
+ }
184
+ finish(result, secrets, maxResultChars) {
116
185
  if (this.states.length === 0)
117
186
  return result;
118
187
  const writes = {
@@ -125,11 +194,8 @@ export class ProgramWrites {
125
194
  // the result, as a direct call reports it: its code and handoff, never
126
195
  // retryable, with the write's outcome unknown.
127
196
  const handoff = this.handoff;
128
- return toolError({
197
+ const facts = {
129
198
  code: handoff?.code ?? "write_outcome_unknown",
130
- message: handoff
131
- ? "A write this program sent failed authorization after it left, so whether it took effect is unknown, and that is the result rather than what the program returned. It will not be sent again. Check its target before doing anything that depends on it, and give nextAction to the operator to recover the connector. Argument echoes may be partial or absent; use the original arguments if reconciliation requires another call."
132
- : "A write this program sent has no known outcome, so that is the result rather than what the program returned. It will not be sent again. Check its target before doing anything that depends on it. Argument echoes may be partial or absent; use the original arguments if reconciliation requires another call.",
133
199
  retryable: false,
134
200
  ...(handoff ? { ...handoff, reconciliationRequired: true, writeOutcome: "unknown" } : {}),
135
201
  ...(this.deadlines.length > 0 ? { details: this.deadlines[0] } : {}),
@@ -141,6 +207,24 @@ export class ProgramWrites {
141
207
  : {}),
142
208
  ...(this.uncertainCallsTruncated ? { uncertainCallsTruncated: true } : {}),
143
209
  writes,
210
+ };
211
+ const advice = handoff
212
+ ? `A write this program sent failed authorization after it left, so its outcome is unknown and the program's return was discarded. It will not be resent. ${CHECK_UNKNOWN_WRITE} Then give what nextAction returns to the operator to recover the connector. ${NO_PROGRAM_RERUN} ${UNKNOWN_WRITE_ARGS}`
213
+ : `A write this program sent has an unknown outcome, and the program's return was discarded. It will not be resent. ${CHECK_UNKNOWN_WRITE} ${NO_PROGRAM_RERUN} ${UNKNOWN_WRITE_ARGS}`;
214
+ const { entries, truncated } = this.report(secrets, Math.min(MAX_REPORT_BYTES, maxResultChars - RESULT_RESERVE - serializedBytes({ error: { ...facts, message: advice } })));
215
+ // The first tool's own words lead, once: they may be the only way forward. An
216
+ // authorization verdict keeps its lead, since recovery starts with the connector.
217
+ const quoted = handoff ? undefined : entries.find((entry) => entry.toolAnswered && entry.message !== undefined);
218
+ const words = quoted?.message;
219
+ if (quoted)
220
+ delete quoted.message;
221
+ return toolError({
222
+ message: quoted
223
+ ? `${quoted.address ?? "A tool"} answered: ${words}\n\nGive the user any approval link or request id above. ${advice}`
224
+ : advice,
225
+ ...(entries.length > 0 ? { unknownWrites: entries } : {}),
226
+ ...(truncated ? { unknownWritesTruncated: true } : {}),
227
+ ...facts,
144
228
  });
145
229
  }
146
230
  return result.isError ? withWrites(result, writes) : result;
@@ -149,20 +233,29 @@ export class ProgramWrites {
149
233
  /**
150
234
  * An error result from a program that sent writes, carrying their counts: a
151
235
  * program that fails after writing is not a program that is safe to repeat.
236
+ * Whatever ended the run, once a write has landed the program is not retryable
237
+ * and no wait window invites it (INV-9). A failed write is proven not applied,
238
+ * so failed writes alone leave the failure's own verdict standing.
152
239
  */
153
240
  function withWrites(result, writes) {
154
241
  const structured = result.structuredContent;
155
242
  const error = structured?.error;
243
+ const { retryAfterMs, ...failure } = error !== null && typeof error === "object"
244
+ ? error
245
+ : {
246
+ retryAfterMs: undefined,
247
+ code: "executor_failed",
248
+ message: result.content[0]?.text ?? "",
249
+ retryable: false,
250
+ };
251
+ const landed = writes.succeeded > 0;
156
252
  const annotated = toolError({
157
- ...(error !== null && typeof error === "object"
158
- ? error
159
- : {
160
- code: "executor_failed",
161
- message: result.content[0]?.text ?? "",
162
- retryable: false,
163
- }),
253
+ ...failure,
254
+ ...(landed ? { retryable: false } : retryAfterMs !== undefined ? { retryAfterMs } : {}),
164
255
  writes,
165
- note: "This run attempted writes, counted in writes; those that succeeded are not undone. Check them before running the task again.",
256
+ note: landed
257
+ ? "This run completed writes, counted in writes, and they are not undone. Running this program again would repeat them: check them, then send only what is left."
258
+ : "This run attempted writes, counted in writes, and none applied. Check them before running the task again.",
166
259
  });
167
260
  if (structured && error !== null && typeof error === "object") {
168
261
  const { error: _replaced, ...rest } = structured;
@@ -127,17 +127,10 @@ export interface ProviderGuideBlocks<O> {
127
127
  export interface ProviderKitFacts {
128
128
  /** `manifest(vendor)`: every operation the provider's tools send, as `"METHOD /path"`. */
129
129
  readonly operations: readonly string[];
130
- /** The `limit` default and maximum every paged read declares. */
131
- readonly pageSize?: {
132
- readonly default: number;
133
- readonly max: number;
134
- };
135
130
  /** The hatch's tool prefix and its reviewed verdict per `"METHOD /path"`. */
136
131
  readonly hatch?: {
137
132
  readonly name: string;
138
133
  readonly verdicts: Readonly<Record<string, "read" | "write" | "destructive" | "refuse">>;
139
- /** Separate-cursor paging opts into the generated hatch paging convention. */
140
- readonly paging?: "argument" | "parameter";
141
134
  readonly fieldsRoot?: string;
142
135
  };
143
136
  /**
@@ -152,12 +145,6 @@ export interface ProviderKitFacts {
152
145
  };
153
146
  /** Whether a write takes an idempotency key the vendor honors, checked against the built tools. */
154
147
  readonly idempotencyKey?: boolean;
155
- /**
156
- * Set only when the hatch refuses a write carrying a withheld placeholder
157
- * value; the Withheld values convention promises exactly that. The kit sets
158
- * it for its standard hatch; kit write builders refuse the same markers.
159
- */
160
- readonly refusesWithheld?: true;
161
148
  }
162
149
  /** Connection facts a provider renders around its maintained guide. */
163
150
  export interface ProviderGuideInput {
package/dist/provider.js CHANGED
@@ -112,13 +112,11 @@ function kitFacts(kit) {
112
112
  }
113
113
  return Object.freeze({
114
114
  operations: Object.freeze([...kit.operations]),
115
- ...(kit.pageSize ? { pageSize: Object.freeze({ ...kit.pageSize }) } : {}),
116
115
  ...(kit.hatch
117
116
  ? {
118
117
  hatch: Object.freeze({
119
118
  name: kit.hatch.name,
120
119
  verdicts: Object.freeze({ ...kit.hatch.verdicts }),
121
- ...(kit.hatch.paging ? { paging: kit.hatch.paging } : {}),
122
120
  ...(kit.hatch.fieldsRoot ? { fieldsRoot: kit.hatch.fieldsRoot } : {}),
123
121
  }),
124
122
  }
@@ -127,7 +125,6 @@ function kitFacts(kit) {
127
125
  ? { errors: Object.freeze({ ...kit.errors, conflicts: Object.freeze([...kit.errors.conflicts]) }) }
128
126
  : {}),
129
127
  ...(kit.idempotencyKey !== undefined ? { idempotencyKey: kit.idempotencyKey } : {}),
130
- ...(kit.refusesWithheld ? { refusesWithheld: true } : {}),
131
128
  });
132
129
  }
133
130
  /**
@@ -9,7 +9,7 @@ import { asRecord, asArray } from "../json.js";
9
9
  // bytes, and failure phases for product recovery.
10
10
  import { Effect, Option } from "effect";
11
11
  import { deriveContext } from "../../../derived-context.js";
12
- import { ConnectorCallError } from "../../../errors.js";
12
+ import { ConnectorCallError, tokenPrefix } from "../../../errors.js";
13
13
  import { Refusal, StatusFailure, toConnectorCallError, } from "../../../http/errors.js";
14
14
  import { Classification, Dispatches, Signal } from "../../../http/services.js";
15
15
  import { callMemo, Operation } from "../../../runtime/call.js";
@@ -65,7 +65,7 @@ export function googleErrors(product, options = {}) {
65
65
  detail: ({ status, body }) => {
66
66
  const message = asRecord(asRecord(body)["error"])["message"];
67
67
  // The body arrives redacted whole, before anything here cuts it.
68
- const said = typeof message === "string" ? message.trim().slice(0, 500) : "";
68
+ const said = typeof message === "string" ? tokenPrefix(message.trim(), 500) : "";
69
69
  return said ? `${provider}: ${said}${/[.!?]$/.test(said) ? "" : "."}` : `${provider} returned HTTP ${status}.`;
70
70
  },
71
71
  classify: [
@@ -3,7 +3,7 @@ import { pathWithholds } from "../_shared/rest/value-safety.js";
3
3
  import { asRecord } from "../_shared/json.js";
4
4
  // Breeze's reviewed GET-only vendor. HTTP methods do not establish safety: see tools.ts.
5
5
  import { Effect, Schema } from "effect";
6
- import { ConnectorCallError, resendAdvice } from "../../errors.js";
6
+ import { ConnectorCallError, resendAdvice, tokenPrefix } from "../../errors.js";
7
7
  import { Refusal } from "../../http/errors.js";
8
8
  import { Credentials, RequestSecrets } from "../../http/services.js";
9
9
  import { get, Summary, vendor } from "../../http/vendor.js";
@@ -555,7 +555,7 @@ function shortText(value, secrets) {
555
555
  // The kit redacted the answer's values and kept its keys; serialized, a key is text
556
556
  // the cut below could split, so the whole text is checked before it is cut.
557
557
  const text = secrets.redact(JSON.stringify(value));
558
- return text.length > 300 ? `${text.slice(0, 300)}…` : text;
558
+ return text.length > 300 ? `${tokenPrefix(text, 300)}…` : text;
559
559
  }
560
560
  /**
561
561
  * Breeze can report a failure inside a 200: `{ success: false, errors: … }`
@@ -1,5 +1,5 @@
1
1
  // Generated by scripts/generate-providers.mjs; do not edit.
2
2
  export const skill = {
3
- "content": "## Church records\n\nBreeze ChMS (now Tithely Church Management) holds people, tags, events,\nattendance, forms, volunteers and giving. People carry contact information,\nbirthdates and family ties; giving reveals donors and amounts. Reduce records\nin code and return only the names, counts or totals the task needs.\n\n## Ids and joins\n\nIds are numeric strings. Resolve them from the relevant inventory before\nwriting. Events have an occurrence id and a series `eventId`; attendance,\nvolunteers and check-in use the occurrence id. Event and pledge lists can lag\nup to 15 minutes behind the site. An all-zero MySQL date means no time was\nrecorded, including no check-out.\n\nProfile `details` use field ids, not names. Resolve field and option ids from\n`list_profile_fields`; multiple-choice filters join option ids with `-`.\nA person-name lookup requires listing people and matching in code. Form entry\nresponses similarly join to that form's field ids.\n\n## Giving\n\nGiving, funds and pledges use archival endpoints removed from the public\nreference in 2023. They can change or vanish without notice. Amounts are\nexact decimal strings: reduce them with integer-cent arithmetic, and read the\ncurrency from `get_account_summary`. A split gift's fund amounts belong to\nits fund ids. Tithe.ly deposits sync in batches, so recent gifts can be absent.\nEditing a giving transaction may return a new payment id.\nUse `limit: 1000` when reducing broad giving ranges: each local page refetches\nthe bounded vendor list, so larger windows avoid repeated full-list fetches.\n\n## Required sequences\n\nCheck for an existing person before adding one: creation does not deduplicate.\nResolve profile fields before updating them. Text, dates (M/D/YYYY) and option\nids use `response`; email, phone, address and family-role values use `details`.\nNamed person writes take these entries in `values`.\nFamily changes can remove previous associations. Attendance check-out records\na departure time; removing attendance deletes the record instead.\n\nBreeze sends writes as GET requests too. Query names ending in `_json` take\nJSON-encoded strings. The named person writes encode their typed profile values\nfor you. The account-log `object_json` is a serialized affected-object value;\nits free-text description has no stable schema. Events and account logs have\nvendor caps: narrow the date range when the result fills its limit.\n\n## Conventions\n\n- **Paging:** while `page.hasMore` is true, continue with `page.nextCursor` unchanged. For `breeze_api_details`, `breeze_api_search`, `list_attendance`, `list_contributions`, `list_funds`, `list_people`, `list_profile_fields`, `list_tags`, pass it as the top-level `cursor`; omit raw paging anchors on continuations. For `breeze_api_read`, put it in `query[page.param]` or `body[page.param]`, as `page.in` specifies. Keep the original method, path, filters, page size, fields and headers. Paged `list_*` and `search_*` reads take `limit` (default 100, at most 1000). The last page has no `nextCursor`. If `page.hasMore` is true without a cursor, the data is incomplete and continuation was withheld. When approaching execute_code's per-program call limit, return page.nextCursor before the call that would exceed it and continue in the next program; don't restart.\n- **Results:** `fields` narrows a read's result to the dot paths you name. `truncated` lists the dot paths a result cut for size; the ids that fetch what was cut stay in the result.\n- **Hatch:** `breeze_api_search` finds this API's operations and reads no account data, `breeze_api_details` shows one operation's contract, and `breeze_api_read` calls a reviewed read. Details' `response.fields` promises selection only; `withheld` hints are incomplete, and absence never means safe or readable. `breeze_api_write` calls a reviewed write; the host may ask the user to confirm it. An operation without a reviewed verdict is refused.\n- **Writes:** a write is sent once and never retried. When its outcome is unknown, read the target before repeating it. Writes take no idempotency key.\n- **Errors:** codes come from the vendor's statuses and error facts, never its prose. `not_found` means the record does not exist; this API tells absence from a permission gap. It documents no conflict.\n- **Withheld values:** a value withheld for safety reads `\"[redacted]\"`. Writes refuse whole marker values and withheld links; longer prose may mention the marker. Paths and queries refuse any containment.",
3
+ "content": "## Church records\n\nBreeze ChMS (now Tithely Church Management) holds people, tags, events,\nattendance, forms, volunteers and giving. People carry contact information,\nbirthdates and family ties; giving reveals donors and amounts. Reduce records\nin code and return only the names, counts or totals the task needs.\n\n## Ids and joins\n\nIds are numeric strings. Resolve them from the relevant inventory before\nwriting. Events have an occurrence id and a series `eventId`; attendance,\nvolunteers and check-in use the occurrence id. Event and pledge lists can lag\nup to 15 minutes behind the site. An all-zero MySQL date means no time was\nrecorded, including no check-out.\n\nProfile `details` use field ids, not names. Resolve field and option ids from\n`list_profile_fields`; multiple-choice filters join option ids with `-`.\nA person-name lookup requires listing people and matching in code. Form entry\nresponses similarly join to that form's field ids.\n\n## Giving\n\nGiving, funds and pledges use archival endpoints removed from the public\nreference in 2023. They can change or vanish without notice. Amounts are\nexact decimal strings: reduce them with integer-cent arithmetic, and read the\ncurrency from `get_account_summary`. A split gift's fund amounts belong to\nits fund ids. Tithe.ly deposits sync in batches, so recent gifts can be absent.\nEditing a giving transaction may return a new payment id.\nUse `limit: 1000` when reducing broad giving ranges: each local page refetches\nthe bounded vendor list, so larger windows avoid repeated full-list fetches.\n\n## Required sequences\n\nCheck for an existing person before adding one: creation does not deduplicate.\nResolve profile fields before updating them. Text, dates (M/D/YYYY) and option\nids use `response`; email, phone, address and family-role values use `details`.\nNamed person writes take these entries in `values`.\nFamily changes can remove previous associations. Attendance check-out records\na departure time; removing attendance deletes the record instead.\n\nBreeze sends writes as GET requests too. Query names ending in `_json` take\nJSON-encoded strings. The named person writes encode their typed profile values\nfor you. The account-log `object_json` is a serialized affected-object value;\nits free-text description has no stable schema. Events and account logs have\nvendor caps: narrow the date range when the result fills its limit.\n\n## Conventions\n\n- **Hatch:** `breeze_api_search` finds operations, `breeze_api_details` shows one contract, `breeze_api_read` calls a reviewed read and `breeze_api_write` a reviewed write.\n- **Errors:** `not_found` means the record does not exist; this API tells absence from a permission gap.",
4
4
  "instructionsHeading": "Church instructions"
5
5
  };
@@ -4,7 +4,7 @@ import { CCB_MEDIA_TYPE, CCB_ENVIRONMENTS, CCB_READ_SCOPES, CCB_WRITE_SCOPES, CC
4
4
  // Pushpay ChMS v2 JSON contracts, OAuth, status meanings, admission and privacy.
5
5
  // The legacy per-church XML API is outside this provider's scope.
6
6
  import { Effect, Schema } from "effect";
7
- import { ConnectorCallError } from "../../errors.js";
7
+ import { ConnectorCallError, tokenPrefix } from "../../errors.js";
8
8
  import { OperationIndex } from "../_shared/rest/operation-index.js";
9
9
  import { checked, indexedVendor } from "../_shared/rest/kit.js";
10
10
  import { valueSafety } from "../_shared/rest/value-safety.js";
@@ -432,7 +432,7 @@ function errorDetail(payload, status) {
432
432
  parts.push(message.trim());
433
433
  }
434
434
  }
435
- const detail = parts.join("; ").slice(0, 500);
435
+ const detail = tokenPrefix(parts.join("; "), 500);
436
436
  return detail ? `CCB HTTP ${status}: ${detail}${/[.!?]$/.test(detail) ? "" : "."}` : `CCB returned HTTP ${status}.`;
437
437
  }
438
438
  export const errors = {
@@ -1,5 +1,5 @@
1
1
  // Generated by scripts/generate-providers.mjs; do not edit.
2
2
  export const skill = {
3
- "content": "## People, households and groups\n\nCCB v2 addresses records by integer ids. Resolve a person from the individual\nlist and a household from families; never guess ids. Family membership and\nchurch group membership join by individual id. A membership role belongs to\nthat group, rather than being a property of the person everywhere.\n\nIdentity Auth acts with the signed-in person's permissions. A permission\nrefusal can mean that person lacks access even when the connection is healthy.\n\n## Events and attendance\n\nAn event is a series; its dated occurrence is a separate identity. Attendance\njoins need both the event id and occurrence. Preserve the vendor's date and\ntime-zone meaning instead of treating every occurrence as UTC midnight.\n\n## Pastoral and financial records\n\nNotes, background checks, pledges and giving metrics can be sensitive. Read\nonly what the task needs and reduce those records inside execute_code before\nreturning an answer. Gift metrics are counts by period, with a range of at\nmost twelve months. Per-gift transactions and amounts belong to the legacy\nv1 XML API, which this v2 connector does not reach.\n\n## Vendor call rates and recovery\n\nCCB meters endpoints separately per API client. Individual profile reads have\nno burst; individual advanced searches allow one call per five seconds, and\ncategory schedule lists allow one per two seconds. Prefer list pages over a\nloop of individual gets. Other deployments sharing the client spend from the\nsame vendor allowance.\n\nFor a profile edit, resolve the individual first and preserve fields outside\nthe requested change. Use `ccb_api_write` for the edit; the host may ask the\nuser to confirm. After an ambiguous update, read that profile to determine\nwhat actually changed before deciding whether another write is needed.\n\n## Conventions\n\n- **Paging:** while `page.hasMore` is true, continue with `page.nextCursor` unchanged. For `ccb_api_details`, `ccb_api_read`, `ccb_api_search`, `list_event_attendees`, `list_events`, `list_families`, `list_group_members`, `list_groups`, `list_individual_groups`, `list_individual_notes`, `list_individuals`, `search_records`, pass it as the top-level `cursor`; omit raw paging anchors on continuations. Keep the original method, path, filters, page size, fields and headers. Paged `list_*` and `search_*` reads take `limit` (default 25, at most 100). The last page has no `nextCursor`. If `page.hasMore` is true without a cursor, the data is incomplete and continuation was withheld. When approaching execute_code's per-program call limit, return page.nextCursor before the call that would exceed it and continue in the next program; don't restart.\n- **Results:** `fields` narrows a read's result to the dot paths you name. `truncated` lists the dot paths a result cut for size; the ids that fetch what was cut stay in the result.\n- **Hatch:** `ccb_api_search` finds this API's operations and reads no account data, `ccb_api_details` shows one operation's contract, and `ccb_api_read` calls a reviewed read. Details' `response.fields` promises selection only; `withheld` hints are incomplete, and absence never means safe or readable. `ccb_api_write` calls a reviewed write; the host may ask the user to confirm it. An operation without a reviewed verdict is refused. 14 operations are refused by design; use `ccb_api_details` with the method and path to see each summary and refusal, even for methods outside the read/write schemas.\n- **Writes:** a write is sent once and never retried. When its outcome is unknown, read the target before repeating it. Writes take no idempotency key.\n- **Errors:** codes come from the vendor's statuses and error facts, never its prose. `not_found` means the record does not exist; this API tells absence from a permission gap. It documents no conflict.\n- **Withheld values:** a value withheld for safety reads `\"[redacted]\"`. Writes refuse whole marker values and withheld links; longer prose may mention the marker. Paths and queries refuse any containment.",
3
+ "content": "## People, households and groups\n\nCCB v2 addresses records by integer ids. Resolve a person from the individual\nlist and a household from families; never guess ids. Family membership and\nchurch group membership join by individual id. A membership role belongs to\nthat group, rather than being a property of the person everywhere.\n\nIdentity Auth acts with the signed-in person's permissions. A permission\nrefusal can mean that person lacks access even when the connection is healthy.\n\n## Events and attendance\n\nAn event is a series; its dated occurrence is a separate identity. Attendance\njoins need both the event id and occurrence. Preserve the vendor's date and\ntime-zone meaning instead of treating every occurrence as UTC midnight.\n\n## Pastoral and financial records\n\nNotes, background checks, pledges and giving metrics can be sensitive. Read\nonly what the task needs and reduce those records inside execute_code before\nreturning an answer. Gift metrics are counts by period, with a range of at\nmost twelve months. Per-gift transactions and amounts belong to the legacy\nv1 XML API, which this v2 connector does not reach.\n\n## Vendor call rates and recovery\n\nCCB meters endpoints separately per API client. Individual profile reads have\nno burst; individual advanced searches allow one call per five seconds, and\ncategory schedule lists allow one per two seconds. Prefer list pages over a\nloop of individual gets. Other deployments sharing the client spend from the\nsame vendor allowance.\n\nFor a profile edit, resolve the individual first and preserve fields outside\nthe requested change. Use `ccb_api_write` for the edit; the host may ask the\nuser to confirm. After an ambiguous update, read that profile to determine\nwhat actually changed before deciding whether another write is needed.\n\n## Conventions\n\n- **Hatch:** `ccb_api_search` finds operations, `ccb_api_details` shows one contract, `ccb_api_read` calls a reviewed read and `ccb_api_write` a reviewed write. 14 operations are refused by design; use `ccb_api_details` with the method and path to see each summary and refusal, even for methods outside the read/write schemas.\n- **Errors:** `not_found` means the record does not exist; this API tells absence from a permission gap.",
4
4
  "instructionsHeading": "Church instructions"
5
5
  };
@@ -4,7 +4,7 @@ import { responseMedia } from "../_shared/rest/response-media.js";
4
4
  import { EXCLUSIVE_PATH_IDS } from "./lib/path-bounds.js";
5
5
  import { RESPONSE_MEDIA } from "./lib/response-media.js";
6
6
  import { Effect, Option, Schema } from "effect";
7
- import { ConnectorCallError } from "../../errors.js";
7
+ import { ConnectorCallError, tokenPrefix } from "../../errors.js";
8
8
  import { Refusal, StatusFailure, toConnectorCallError, } from "../../http/errors.js";
9
9
  import { Budget, Credentials, Classification, Dispatches, Signal } from "../../http/services.js";
10
10
  import { restSafety } from "../../http/safety.js";
@@ -236,7 +236,7 @@ export function describeErrors(errors) {
236
236
  continue;
237
237
  }
238
238
  const code = typeof entry.code === "number" ? entry.code : undefined;
239
- const message = typeof entry.message === "string" ? entry.message.slice(0, 500) : "Unknown error";
239
+ const message = (typeof entry.message === "string" && tokenPrefix(entry.message, 500)) || "Unknown error";
240
240
  parts.push(code === undefined ? message : `${code}: ${message}`);
241
241
  if (Array.isArray(entry.error_chain))
242
242
  walk(entry.error_chain);
@@ -116,6 +116,8 @@ export const cloudflare = defineApiProvider({
116
116
  defaultCallAdmission: (options) => admissionPolicy(options.maxConcurrency ?? 6),
117
117
  credential: (options) => (options.auth.type === "globalApiKey" ? GLOBAL_API_KEY_CREDENTIAL : API_TOKEN_CREDENTIAL),
118
118
  authenticate,
119
+ // The Global API Key scheme's account email names the account; X-Auth-Key proves it.
120
+ publicIdentifierHeaders: ["x-auth-email"],
119
121
  errors: cloudflareErrors,
120
122
  inBandRefusals: true,
121
123
  idempotencyKey: false,
@@ -1,5 +1,5 @@
1
1
  // Generated by scripts/generate-providers.mjs; do not edit.
2
2
  export const skill = {
3
- "content": "## Accounts, domains and records\n\nResolve account and zone ids before acting. A zone is a domain, found by its name;\nits account id joins it to the account inventory. Cloudflare ids are normally\n32-character hex strings. A default id routes a request; a pin limits its scope.\n\nDNS TTL is in seconds; TTL 1 means automatic. Read the record before changing it.\nPATCH changes supplied fields; PUT replaces the record, so preserve its type,\nname and content. Read state before replacing, rolling back or purging. Prefer\na targeted cache purge by files, tags, hosts or prefixes over purging everything.\n\nHatch data keeps Cloudflare's vendor envelope. For example, select DNS record\nidentities with `fields: [\"result.id\", \"result.name\"]`; a list stays under\n`data.result`. Named account and zone lists keep projected rows with camelCase\naccount and name-server fields.\n\n## Workers, storage and analytics\n\nWorker script source is code; secret binding values are separate and unavailable.\nKV values are application data. Encode a slash inside a KV key or R2 object key\nas `%2F`. R2 requires API-token authentication, rather than a Global API Key.\n\nGraphQL Analytics uses accountTag and zoneTag for account/zone identities.\nFilter every accounts/zones field by equality or an explicit set of those ids.\nA pinned query uses one operation per document and every declared fragment.\nAnalytics Engine SQL is read-only; D1 SQL and Durable Object queries can write.\nAI inference, previews and outbound checks can cost money or activate work.\n\n## Disqualifiers and recovery\n\nCredential minting, rotation, stored secrets, private session captures and signed\nupload or deployment links are unavailable. Use the Cloudflare dashboard for\ncredential administration. A Global API Key cannot change user settings or\nmemberships through this connection.\n\nAfter an authentication failure, use `verify_credential` once to distinguish a\nrejected credential from a permission gap. An operator replaces the credential;\nrepeating the failed request does not add permissions. Cloudflare's global limit\ncounts dashboard traffic and all tokens together, so reduce requests as well as\nhonoring the returned wait.\n\n## Conventions\n\n- **Paging:** while `page.hasMore` is true, continue with `page.nextCursor` unchanged. For `cloudflare_api_details`, `cloudflare_api_search`, `list_accounts`, `list_zones`, pass it as the top-level `cursor`; omit raw paging anchors on continuations. For `cloudflare_api_read`, put it in `query[page.param]` or `body[page.param]`, as `page.in` specifies. Keep the original method, path, filters, page size, fields and headers. Paged `list_*` and `search_*` reads take `limit` (default 20, at most 50). The last page has no `nextCursor`. If `page.hasMore` is true without a cursor, the data is incomplete and continuation was withheld. When approaching execute_code's per-program call limit, return page.nextCursor before the call that would exceed it and continue in the next program; don't restart.\n- **Results:** `fields` narrows a read's result to the dot paths you name. `truncated` lists the dot paths a result cut for size; the ids that fetch what was cut stay in the result.\n- **Envelope fields:** hatch data keeps the vendor envelope. Select declared fields with `fields: [\"result.id\", \"result.name\"]`; array traversal also accepts `result[].id`. Named reads keep their projected fields.\n- **Hatch:** `cloudflare_api_search` finds this API's operations and reads no account data, `cloudflare_api_details` shows one operation's contract, and `cloudflare_api_read` calls a reviewed read. Details' `response.fields` promises selection only; `withheld` hints are incomplete, and absence never means safe or readable. `cloudflare_api_write` calls a reviewed write; the host may ask the user to confirm it. An operation without a reviewed verdict is refused. 70 operations are refused by design; use `cloudflare_api_details` with the method and path to see each summary and refusal, even for methods outside the read/write schemas.\n- **Writes:** a write is sent once and never retried. When its outcome is unknown, read the target before repeating it. Writes take no idempotency key.\n- **Errors:** codes come from the vendor's statuses and error facts, never its prose. `not_found` means the record does not exist; this API tells absence from a permission gap. It documents no conflict.\n- **Withheld values:** a value withheld for safety reads `\"[redacted]\"`. Writes refuse whole marker values and withheld links; longer prose may mention the marker. Paths and queries refuse any containment.",
3
+ "content": "## Accounts, domains and records\n\nResolve account and zone ids before acting. A zone is a domain, found by its name;\nits account id joins it to the account inventory. Cloudflare ids are normally\n32-character hex strings. A default id routes a request; a pin limits its scope.\n\nDNS TTL is in seconds; TTL 1 means automatic. Read the record before changing it.\nPATCH changes supplied fields; PUT replaces the record, so preserve its type,\nname and content. Read state before replacing, rolling back or purging. Prefer\na targeted cache purge by files, tags, hosts or prefixes over purging everything.\n\nA hatch list stays under `data.result`. Named account and zone lists use camelCase\naccount and name-server fields.\n\n## Workers, storage and analytics\n\nWorker script source is code; secret binding values are separate and unavailable.\nKV values are application data. Encode a slash inside a KV key or R2 object key\nas `%2F`. R2 requires API-token authentication, rather than a Global API Key.\n\nGraphQL Analytics uses accountTag and zoneTag for account/zone identities.\nFilter every accounts/zones field by equality or an explicit set of those ids.\nA pinned query uses one operation per document and every declared fragment.\nAnalytics Engine SQL is read-only; D1 SQL and Durable Object queries can write.\nAI inference, previews and outbound checks can cost money or activate work.\n\n## Disqualifiers and recovery\n\nCredential minting, rotation, stored secrets, private session captures and signed\nupload or deployment links are unavailable. Use the Cloudflare dashboard for\ncredential administration. A Global API Key cannot change user settings or\nmemberships through this connection.\n\nAfter an authentication failure, use `verify_credential` once to distinguish a\nrejected credential from a permission gap. An operator replaces the credential;\nrepeating the failed request does not add permissions. Cloudflare's global limit\ncounts dashboard traffic and all tokens together, so reduce requests as well as\nhonoring the returned wait.\n\n## Conventions\n\n- **Envelope fields:** hatch data keeps the vendor envelope: select `result.id`, or `result[].id` across a list. Named reads keep their projected fields.\n- **Hatch:** `cloudflare_api_search` finds operations, `cloudflare_api_details` shows one contract, `cloudflare_api_read` calls a reviewed read and `cloudflare_api_write` a reviewed write. 70 operations are refused by design; use `cloudflare_api_details` with the method and path to see each summary and refusal, even for methods outside the read/write schemas.\n- **Errors:** `not_found` means the record does not exist; this API tells absence from a permission gap.",
4
4
  "instructionsHeading": "Account instructions"
5
5
  };
@@ -1,5 +1,5 @@
1
1
  // Generated by scripts/generate-providers.mjs; do not edit.
2
2
  export const skill = {
3
- "content": "## Find the document first\n\nDocument ids join Docs to Drive: take the part after `/d/` in a document URL,\nor find the file by name through a Google Drive connection. An id the person\nprovides works as is. Docs has no\naccount-data search or listing; its operation search discovers API methods.\nA missing document may be absent or inaccessible to the mapped Workspace user.\n\n## Read before editing\n\nThe rendering follows document tabs, including nested tabs. Headers, footers,\nfloating images and comments are not rendered; `notRendered` names omitted\nkinds. Suggested insertions and deletions can appear together without marks.\nAn accepted or rejected suggestions preview cannot supply edit indexes.\nIndexed rows page with `page.nextCursor`; keep the same arguments, starting\nwith `maxChars: 0` when only indexes are needed.\n\nUse the inline view's UTF-16 start/end indexes and revision to plan edits.\nIndexes shift after each edit, including earlier requests in the same atomic\nbatch. Apply independent deletions from the end backwards. A revision is a\nprecondition, never an idempotency key. Re-read and recompute after a revision\nconflict.\n\nStyles, named ranges, headers and footers live in the complete document\nresource. Read it with\n`docs_api_read({path:\"/documents/doc_123\",query:{includeTabsContent:true}})`\nwhen a rendering cannot answer the task. Paths omit `/v1`; the hatch reply is\n`{status,data}`, with the vendor document in `data` (a program call's\n`delivery.data.data`). A Docs\ncreate payload sets a title only; initial text is a separate edit. Appending\njoins the final paragraph, so begin with a newline when a new paragraph is\nintended. A broad text replacement can rewrite every tab.\nBatch kinds include `updateTextStyle` for formatting, `insertTable`,\n`createParagraphBullets`, and `deleteContentRange`.\n\n## Recover a partial create\n\nIf creation succeeded but initial text failed, keep the created document id.\nAppend the missing text directly only when the failure proves Google refused\nit. Otherwise read that document and add only what is missing. Creating again\nwould make another document. If the create reply lost its id, search Drive for\nthe title before attempting a fresh create.\n\nPreview comment and suggestion mutations are unavailable. Comments belong to\nDrive. Copying a template or choosing a destination folder also belongs to\nDrive; a Docs create starts in My Drive's root.\n\n## Conventions\n\n- **Paging:** while `page.hasMore` is true, continue with `page.nextCursor` unchanged. For `docs_api_details`, `docs_api_search`, `get_document`, pass it as the top-level `cursor`; omit raw paging anchors on continuations. Keep the original method, path, filters, page size, fields and headers. The last page has no `nextCursor`. If `page.hasMore` is true without a cursor, the data is incomplete and continuation was withheld. When approaching execute_code's per-program call limit, return page.nextCursor before the call that would exceed it and continue in the next program; don't restart.\n- **Results:** `fields` narrows a read's result to the dot paths you name. `truncated` lists the dot paths a result cut for size; the ids that fetch what was cut stay in the result.\n- **Hatch:** `docs_api_search` finds this API's operations and reads no account data, `docs_api_details` shows one operation's contract, and `docs_api_read` calls a reviewed read. Details' `response.fields` promises selection only; `withheld` hints are incomplete, and absence never means safe or readable. `docs_api_write` calls a reviewed write; the host may ask the user to confirm it. An operation without a reviewed verdict is refused.\n- **Writes:** a write is sent once and never retried. When its outcome is unknown, read the target before repeating it. Writes take no idempotency key.\n- **Errors:** codes come from the vendor's statuses and error facts, never its prose. An unknown id, or one these credentials cannot reach, fails as `connector_call_failed`; this API does not tell the two apart. `conflict` means: A revision-guarded batch update returned ABORTED or FAILED_PRECONDITION with HTTP 400 or 409; The document content or revision changed during indexed content paging.\n- **Withheld values:** a value withheld for safety reads `\"[redacted]\"`. Writes refuse whole marker values and withheld links; longer prose may mention the marker. Paths and queries refuse any containment.",
3
+ "content": "## Find the document first\n\nDocument ids join Docs to Drive: take the part after `/d/` in a document URL,\nor find the file by name through a Google Drive connection. An id the person\nprovides works as is. Docs has no\naccount-data search or listing; its operation search discovers API methods.\nA missing document may be absent or inaccessible to the mapped Workspace user.\n\n## Read before editing\n\nThe rendering follows document tabs, including nested tabs. Headers, footers,\nfloating images and comments are not rendered; `notRendered` names omitted\nkinds. Suggested insertions and deletions can appear together without marks.\nAn accepted or rejected suggestions preview cannot supply edit indexes.\nIndexed rows page with `page.nextCursor`; keep the same arguments, starting\nwith `maxChars: 0` when only indexes are needed.\n\nUse the inline view's UTF-16 start/end indexes and revision to plan edits.\nIndexes shift after each edit, including earlier requests in the same atomic\nbatch. Apply independent deletions from the end backwards. A revision is a\nprecondition, never an idempotency key. Re-read and recompute after a revision\nconflict.\n\nStyles, named ranges, headers and footers live in the complete document\nresource. Read it with\n`docs_api_read({path:\"/documents/<id>\",query:{includeTabsContent:true}})`\nwhen a rendering cannot answer the task. Paths omit `/v1`; the hatch reply is\n`{status,data}`, with the vendor document in `data` (a program call's\n`delivery.data.data`). A Docs\ncreate payload sets a title only; initial text is a separate edit. Appending\njoins the final paragraph, so begin with a newline when a new paragraph is\nintended. A broad text replacement can rewrite every tab.\nBatch kinds include `updateTextStyle` for formatting, `insertTable`,\n`createParagraphBullets`, and `deleteContentRange`.\n\n## Recover a partial create\n\nIf creation succeeded but initial text failed, keep the created document id.\nAppend the missing text directly only when the failure proves Google refused\nit. Otherwise read that document and add only what is missing. Creating again\nwould make another document. If the create reply lost its id, search Drive for\nthe title before attempting a fresh create.\n\nPreview comment and suggestion mutations are unavailable. Comments belong to\nDrive. Copying a template or choosing a destination folder also belongs to\nDrive; a Docs create starts in My Drive's root.\n\n## Conventions\n\n- **Hatch:** `docs_api_search` finds operations, `docs_api_details` shows one contract, `docs_api_read` calls a reviewed read and `docs_api_write` a reviewed write.\n- **Errors:** An unknown id, or one these credentials cannot reach, fails as `connector_call_failed`; this API does not tell the two apart. `conflict` means: A revision-guarded batch update returned ABORTED or FAILED_PRECONDITION with HTTP 400 or 409; The document content or revision changed during indexed content paging.",
4
4
  "instructionsHeading": "Connection instructions"
5
5
  };
@@ -1,5 +1,5 @@
1
1
  // Generated by scripts/generate-providers.mjs; do not edit.
2
2
  export const skill = {
3
- "content": "## Finding files and joining Workspace products\n\nDrive searches file data; operation discovery searches API capabilities.\nUse file ids from reads, Drive URLs or the person: an id they give, such as\n`pdf1`, works as is. The same id identifies a Google Doc,\nSheet, Slides deck or Form in its product connector. Shared-drive ids identify\nmembership and the root folder; folder parents determine inherited access.\nA missing file and a file the delegated user cannot see fail alike.\n\nDrive query values use single quotes: `name contains 'budget'`,\n`mimeType = 'application/pdf'`, `'folder_123' in parents`, or\n`modifiedTime > '2026-01-01T00:00:00'`. Trashed files are excluded by default.\n`incompleteSearch: true` means Google did not search every corpus; narrow the\nquery or choose a single shared drive. It is never an exact count.\n\n`maxBytes` is the byte budget and `limit` is rows per page; keep a high limit\nfor large folders and continue with the same listing arguments. Named `fields`\nmay narrow and `maxBytes` may change between pages. Hatch paths omit `/drive/v3`.\n\n## Content and file decisions\n\nThe content workflow exports Docs as Markdown, Sheets as CSV of the first\nsheet only, and Slides as text. Other text files retain their declared charset.\nSmall binary files can be returned as base64. A drawing, unsupported native\nfile, folder, shortcut or oversized binary returns an unavailable explanation.\nFor a shortcut, read its target id instead. Drive exports at most 10 MB.\n\nContent containing sent credentials is withheld as a whole binary value; there\nis no partial-content recovery around that boundary. Withheld MIME, shortcut,\nfolder or parent metadata cannot decide a subsequent read or move. Re-read\nthrough a safe source before trying the workflow again.\n\n## Access and recovery\n\nA file or copy inherits its destination folder's access. Read permission holders\nbefore moving, copying or creating in a shared folder, and read them again after\nchanging access. Inherited permissions name the parent folder or shared drive\nthat must be changed instead. An owner cannot be removed or replaced here.\n\nAll new grants use `share_file`, which sends no notification email by default.\nConfirm intent before granting `anyone` or domain access. Permission creation\nthrough the hatch is refused, including public-link grants; the named workflow\nowns grantee validation and notification behavior.\n\nContent updates replace the complete content. A create with an uncertain outcome\nneeds a search for the intended file before another create. Recoverable trash\ncan be restored for 30 days. Permanent deletion, ownership transfer, watch\nregistration, credential minting and generic media transport are outside the\nhatch. Use the named content and upload workflows for bounded bytes.\n\n## Conventions\n\n- **Paging:** while `page.hasMore` is true, continue with `page.nextCursor` unchanged. For `drive_api_details`, `drive_api_read`, `drive_api_search`, `list_folder_items`, `list_permissions`, `list_shared_drives`, `search_files`, pass it as the top-level `cursor`; omit raw paging anchors on continuations. Keep the original method, path, filters, page size, fields and headers. Paged `list_*` and `search_*` reads take `limit` (default 25, at most 100). The last page has no `nextCursor`. If `page.hasMore` is true without a cursor, the data is incomplete and continuation was withheld. When approaching execute_code's per-program call limit, return page.nextCursor before the call that would exceed it and continue in the next program; don't restart.\n- **Results:** `fields` narrows a read's result to the dot paths you name. `truncated` lists the dot paths a result cut for size; the ids that fetch what was cut stay in the result.\n- **Hatch:** `drive_api_search` finds this API's operations and reads no account data, `drive_api_details` shows one operation's contract, and `drive_api_read` calls a reviewed read. Details' `response.fields` promises selection only; `withheld` hints are incomplete, and absence never means safe or readable. `drive_api_write` calls a reviewed write; the host may ask the user to confirm it. An operation without a reviewed verdict is refused. 51 operations are refused by design; use `drive_api_details` with the method and path to see each summary and refusal, even for methods outside the read/write schemas.\n- **Writes:** a write is sent once and never retried. When its outcome is unknown, read the target before repeating it. Writes take no idempotency key.\n- **Errors:** codes come from the vendor's statuses and error facts, never its prose. An unknown id, or one these credentials cannot reach, fails as `connector_call_failed`; this API does not tell the two apart. `conflict` means: A Drive source page changed before its local cursor resumed.\n- **Withheld values:** a value withheld for safety reads `\"[redacted]\"`. Writes refuse whole marker values and withheld links; longer prose may mention the marker. Paths and queries refuse any containment.",
3
+ "content": "## Finding files and joining Workspace products\n\nDrive searches file data; operation discovery searches API capabilities.\nUse file ids from reads, Drive URLs or the person: an id they give works as is.\nThe same id identifies a Google Doc, Sheet, Slides deck or Form in its product\nconnector. Shared-drive ids identify\nmembership and the root folder; folder parents determine inherited access.\nA missing file and a file the delegated user cannot see fail alike.\n\nDrive query values use single quotes: `name contains 'budget'`,\n`mimeType = 'application/pdf'`, `'folder_123' in parents`, or\n`modifiedTime > '2026-01-01T00:00:00'`. Trashed files are excluded by default.\n`incompleteSearch: true` means Google did not search every corpus; narrow the\nquery or choose a single shared drive. It is never an exact count.\n\n`maxBytes` is the byte budget and `limit` is rows per page; keep a high limit\nfor large folders and continue with the same listing arguments. Named `fields`\nmay narrow and `maxBytes` may change between pages. Hatch paths omit `/drive/v3`.\n\n## Content and file decisions\n\nThe content workflow exports Docs as Markdown, Sheets as CSV of the first\nsheet only, and Slides as text. Other text files retain their declared charset.\nSmall binary files can be returned as base64. A drawing, unsupported native\nfile, folder, shortcut or oversized binary returns an unavailable explanation.\nFor a shortcut, read its target id instead. Drive exports at most 10 MB.\n\nContent containing sent credentials is withheld as a whole binary value; there\nis no partial-content recovery around that boundary. Withheld MIME, shortcut,\nfolder or parent metadata cannot decide a subsequent read or move. Re-read\nthrough a safe source before trying the workflow again.\n\n## Access and recovery\n\nA file or copy inherits its destination folder's access. Read permission holders\nbefore moving, copying or creating in a shared folder, and read them again after\nchanging access. Inherited permissions name the parent folder or shared drive\nthat must be changed instead. An owner cannot be removed or replaced here.\n\nAll new grants use `share_file`, which sends no notification email by default.\nConfirm intent before granting `anyone` or domain access. Permission creation\nthrough the hatch is refused, including public-link grants; the named workflow\nowns grantee validation and notification behavior.\n\nContent updates replace the complete content. A create with an uncertain outcome\nneeds a search for the intended file before another create. Recoverable trash\ncan be restored for 30 days. Permanent deletion, ownership transfer, watch\nregistration, credential minting and generic media transport are outside the\nhatch. Use the named content and upload workflows for bounded bytes.\n\n## Conventions\n\n- **Hatch:** `drive_api_search` finds operations, `drive_api_details` shows one contract, `drive_api_read` calls a reviewed read and `drive_api_write` a reviewed write. 51 operations are refused by design; use `drive_api_details` with the method and path to see each summary and refusal, even for methods outside the read/write schemas.\n- **Errors:** An unknown id, or one these credentials cannot reach, fails as `connector_call_failed`; this API does not tell the two apart. `conflict` means: A Drive source page changed before its local cursor resumed.",
4
4
  "instructionsHeading": "Connection instructions"
5
5
  };
@@ -94,6 +94,7 @@ export const forms = defineApiProvider({
94
94
  errors,
95
95
  safety: () => safety,
96
96
  probe,
97
+ blocks: { keys: ["writes"], select: (options) => (options.readOnly ? [] : ["writes"]) },
97
98
  guide: (options) => ({
98
99
  context: [
99
100
  `Purpose: ${options.purpose}`,
@@ -1,5 +1,5 @@
1
1
  // Generated by scripts/generate-providers.mjs; do not edit.
2
2
  export const skill = {
3
- "content": "## Finding forms and joining answers\n\nThe Forms API cannot list or search forms. Find the file through Drive, or use\nthe id after `/forms/d/` in its edit URL. The `/forms/d/e/…` responder link is\nnot a form id. A form id is also its Drive file id.\n\nA form contains items; an item may ask one question, several grid questions,\nor no question. Responses join by `questionId`, never by `itemId`. Named\nresponse reads label answers with the current question titles in form order;\nan answer to a deleted question has no title. A response may contain several\nvalues or uploaded-file references for one question. File ids join to Drive.\n\n## Reading complete responses\n\nResponses and respondent emails can contain personal information. Filter and\naggregate in `execute_code`, returning named answers only when the task needs\nthem. `submittedAfter` is exclusive UTC: retain the newest `lastSubmittedTime`\nfor the next incremental read. Google does not promise a stable response\nsnapshot. A continuation inside a changed vendor page fails `conflict`; start\nagain or use that newest timestamp.\n\nLarge grids continue inside the same item: `questionsFrom` places the returned\nrows. `omittedQuestionIds` identifies answers left out for size; read those ids\nwith `get_response`. The structural hatch exposes quiz grading and correct\nanswers, images, and option navigation that named projections omit. Temporary\nimage access destinations remain withheld.\n\n## Editing without losing structure\n\nRead the form's current revision before editing. A stale revision refuses the\nbatch as `conflict`; re-read and rebuild the requested edit. A revision guards\nconcurrent changes and does not make a repeated write safe.\n\nCreation sets only title and Drive file name. Add descriptions or questions\nafterward. `updateItem` can replace grading, images and section navigation;\nread the full item through the hatch first and use a precise `updateMask`.\nItem locations are indexes, while response answers use question ids.\n\nPublishing, sharing, deleting forms, and watch administration are outside this\nconnection. Responses are read-only; no tool submits or deletes a response.\n\n## Conventions\n\n- **Paging:** while `page.hasMore` is true, continue with `page.nextCursor` unchanged. For `forms_api_details`, `forms_api_read`, `forms_api_search`, `get_form`, `list_responses`, pass it as the top-level `cursor`; omit raw paging anchors on continuations. Keep the original method, path, filters, page size, fields and headers. Paged `list_*` and `search_*` reads take `limit` (default 25, at most 100). The last page has no `nextCursor`. If `page.hasMore` is true without a cursor, the data is incomplete and continuation was withheld. When approaching execute_code's per-program call limit, return page.nextCursor before the call that would exceed it and continue in the next program; don't restart.\n- **Results:** `fields` narrows a read's result to the dot paths you name. `truncated` lists the dot paths a result cut for size; the ids that fetch what was cut stay in the result.\n- **Hatch:** `forms_api_search` finds this API's operations and reads no account data, `forms_api_details` shows one operation's contract, and `forms_api_read` calls a reviewed read. Details' `response.fields` promises selection only; `withheld` hints are incomplete, and absence never means safe or readable. `forms_api_write` calls a reviewed write; the host may ask the user to confirm it. An operation without a reviewed verdict is refused. 5 operations are refused by design; use `forms_api_details` with the method and path to see each summary and refusal, even for methods outside the read/write schemas.\n- **Writes:** a write is sent once and never retried. When its outcome is unknown, read the target before repeating it. Writes take no idempotency key.\n- **Errors:** codes come from the vendor's statuses and error facts, never its prose. An unknown id, or one these credentials cannot reach, fails as `connector_call_failed`; this API does not tell the two apart. `conflict` means: A revision-guarded batch update returned ABORTED or FAILED_PRECONDITION with HTTP 400 or 409.\n- **Withheld values:** a value withheld for safety reads `\"[redacted]\"`. Writes refuse whole marker values and withheld links; longer prose may mention the marker. Paths and queries refuse any containment.",
3
+ "content": "## Finding forms and joining answers\n\nThe Forms API cannot list or search forms. Find the file through Drive, or use\nthe id after `/forms/d/` in its edit URL. The `/forms/d/e/…` responder link is\nnot a form id. A form id is also its Drive file id.\n\nA form contains items; an item may ask one question, several grid questions,\nor no question. Responses join by `questionId`, never by `itemId`. Named\nresponse reads label answers with the current question titles in form order;\nan answer to a deleted question has no title. A response may contain several\nvalues or uploaded-file references for one question. File ids join to Drive.\n\n## Reading complete responses\n\nResponses and respondent emails can contain personal information. Filter and\naggregate in `execute_code`, returning named answers only when the task needs\nthem. `submittedAfter` is exclusive UTC: retain the newest `lastSubmittedTime`\nfor the next incremental read. Google does not promise a stable response\nsnapshot. A continuation inside a changed vendor page fails `conflict`; start\nagain or use that newest timestamp.\n\nLarge grids continue inside the same item: `questionsFrom` places the returned\nrows. `omittedQuestionIds` identifies answers left out for size; read those ids\nwith `get_response`. The structural hatch exposes quiz grading and correct\nanswers, images, and option navigation that named projections omit. Temporary\nimage access destinations remain withheld.\n\n<!-- when: writes -->\n## Editing without losing structure\n\nRead the form's current revision before editing. A stale revision refuses the\nbatch as `conflict`; re-read and rebuild the requested edit. A revision guards\nconcurrent changes and does not make a repeated write safe.\n\nCreation sets only title and Drive file name. Add descriptions or questions\nafterward. `updateItem` can replace grading, images and section navigation;\nread the full item through the hatch first and use a precise `updateMask`.\nItem locations are indexes, while response answers use question ids.\n\nPublishing, sharing, deleting forms, and watch administration are outside this\nconnection. Responses are read-only; no tool submits or deletes a response.\n<!-- end -->\n\n## Conventions\n\n- **Hatch:** `forms_api_search` finds operations, `forms_api_details` shows one contract, `forms_api_read` calls a reviewed read and `forms_api_write` a reviewed write. 5 operations are refused by design; use `forms_api_details` with the method and path to see each summary and refusal, even for methods outside the read/write schemas.\n- **Errors:** An unknown id, or one these credentials cannot reach, fails as `connector_call_failed`; this API does not tell the two apart. `conflict` means: A revision-guarded batch update returned ABORTED or FAILED_PRECONDITION with HTTP 400 or 409.",
4
4
  "instructionsHeading": "Connection instructions"
5
5
  };