@zackbart/connecta 0.37.1 → 0.39.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.
- package/CHANGELOG.md +94 -0
- package/README.md +5 -4
- package/bin/connecta.mjs +1 -39
- package/bin/version.mjs +1 -1
- package/dist/access-tokens.js +2 -4
- package/dist/agent-identity.js +1 -1
- package/dist/agent-routes.js +231 -0
- package/dist/auth/clerk-sdk/generated.js +2 -1
- package/dist/auth/downstream-oauth/consent.js +519 -0
- package/dist/auth/downstream-oauth/grant-store.js +439 -0
- package/dist/auth/downstream-oauth/refresh-coordinator.js +562 -0
- package/dist/auth/downstream-oauth/refresh-records.js +447 -0
- package/dist/auth/downstream-oauth/sdk-hooks.js +242 -0
- package/dist/auth/downstream-oauth.js +66 -2516
- package/dist/auth/downstream-revocation.js +1 -1
- package/dist/auth/native.js +27 -0
- package/dist/auth/oauth-grant.js +24 -0
- package/dist/auth/oauth-token-response.js +1 -1
- package/dist/auth/static-oauth.js +153 -131
- package/dist/auth/vercel-agent.js +2 -1
- package/dist/auth-elicitation.js +9 -3
- package/dist/authorization-handoff.js +11 -5
- package/dist/branding.d.ts +1 -15
- package/dist/branding.js +2 -2
- package/dist/bytes.js +42 -0
- package/dist/call-admission-context.js +2 -5
- package/dist/call-recovery.js +0 -3
- package/dist/catalog-cache.js +130 -43
- package/dist/catalog-fingerprint.js +2 -18
- package/dist/catalog-policy.js +18 -0
- package/dist/catalog-service.js +285 -48
- package/dist/catalog.js +18 -2
- package/dist/config-defaults.d.ts +1 -0
- package/dist/config-defaults.js +1 -0
- package/dist/config-schema.d.ts +1 -1
- package/dist/config-schema.js +6 -2
- package/dist/config.d.ts +57 -25
- package/dist/config.js +30 -77
- package/dist/connector-caller.d.ts +1 -1
- package/dist/connector-health.js +59 -0
- package/dist/connector-status.js +117 -0
- package/dist/connectors/api-connector.d.ts +7 -2
- package/dist/connectors/api-connector.js +11 -7
- package/dist/connectors/guarded-fetch.d.ts +0 -6
- package/dist/connectors/guarded-fetch.js +1 -11
- package/dist/connectors/negotiation-cache.js +22 -4
- package/dist/connectors/remote-mcp/call.js +164 -0
- package/dist/connectors/remote-mcp/catalog.js +278 -0
- package/dist/connectors/remote-mcp/config.js +263 -0
- package/dist/connectors/remote-mcp/connection.js +671 -0
- package/dist/connectors/remote-mcp/errors.js +488 -0
- package/dist/connectors/remote-mcp/oauth.js +469 -0
- package/dist/connectors/remote-mcp/resources.js +473 -0
- package/dist/connectors/remote-mcp/runtime.js +1 -0
- package/dist/connectors/remote-mcp/transport.js +544 -0
- package/dist/connectors/remote-mcp.js +61 -2898
- package/dist/connectors/resource-uri.js +0 -4
- package/dist/credential-contract.d.ts +23 -0
- package/dist/credential-reads.js +17 -0
- package/dist/credential-verification.js +53 -0
- package/dist/credentials.d.ts +7 -0
- package/dist/credentials.js +60 -25
- package/dist/d1.d.ts +0 -89
- package/dist/d1.js +0 -34
- package/dist/describe-config.d.ts +12 -2
- package/dist/describe-config.js +2 -0
- package/dist/discovery-health.js +199 -0
- package/dist/downstream-input.js +4 -2
- package/dist/errors.d.ts +55 -17
- package/dist/errors.js +95 -41
- package/dist/execute.js +172 -125
- package/dist/executor-result.js +5 -2
- package/dist/executors/quickjs-runtime.js +2 -25
- package/dist/executors/quickjs.js +1 -1
- package/dist/guest-functions.js +83 -0
- package/dist/guest-runtime.js +63 -20
- package/dist/guest-types.d.ts +24 -10
- package/dist/http/argument-problems.js +35 -0
- package/dist/http/client.js +1 -1
- package/dist/http/errors.js +7 -6
- package/dist/http/fields.js +192 -13
- package/dist/http/hatch-response.js +73 -0
- package/dist/http/hatch.js +88 -23
- package/dist/http/json-schema.js +1 -1
- package/dist/http/node-transport.js +0 -1
- package/dist/http/oauth.js +4 -3
- package/dist/http/paging.js +6 -9
- package/dist/http/provider.js +33 -5
- package/dist/http/safety.js +9 -1
- package/dist/http/services.js +1 -8
- package/dist/http/tools.js +45 -30
- package/dist/http/vendor.js +7 -7
- package/dist/identity.js +2 -6
- package/dist/index.d.ts +1 -1
- package/dist/index.js +9 -2
- package/dist/invocation-auth.js +2 -1
- package/dist/invocation.js +21 -6
- package/dist/membership.d.ts +13 -5
- package/dist/membership.js +34 -2
- package/dist/meta-tools.js +50 -35
- package/dist/native-recovery.js +4 -16
- package/dist/native-tools.js +6 -2
- package/dist/oauth-handoff.js +2 -2
- package/dist/oauth-sealing.js +10 -0
- package/dist/operation-vocabulary.js +136 -14
- package/dist/operator-record.d.ts +2 -2
- package/dist/operator-record.js +2 -49
- package/dist/operator-ui/contract.d.ts +12 -2
- package/dist/operator-ui/generated.js +3 -3
- package/dist/operator-ui/model.d.ts +22 -1
- package/dist/program-writes.js +3 -8
- package/dist/provider-call.js +0 -7
- package/dist/provider.d.ts +1 -1
- package/dist/providers/_shared/google/kit.js +1 -6
- package/dist/providers/_shared/google/service-account.js +1 -3
- package/dist/providers/_shared/google/workspace.d.ts +1 -1
- package/dist/providers/_shared/guide.js +3 -0
- package/dist/providers/_shared/json.js +12 -0
- package/dist/providers/_shared/rest/compact-index.js +1 -1
- package/dist/providers/_shared/rest/kit.js +1 -3
- package/dist/providers/_shared/rest/operation-index.js +49 -53
- package/dist/providers/_shared/rest/value-safety.js +106 -28
- package/dist/providers/breeze/api.js +9 -8
- package/dist/providers/breeze/index.js +4 -2
- package/dist/providers/breeze/skill.generated.js +1 -1
- package/dist/providers/breeze/tools.js +7 -7
- package/dist/providers/ccb/api.js +3 -7
- package/dist/providers/ccb/index.js +7 -1
- package/dist/providers/ccb/skill.generated.js +1 -1
- package/dist/providers/ccb/tools.js +1 -6
- package/dist/providers/cloudflare/api.js +10 -3
- package/dist/providers/cloudflare/index.js +2 -1
- package/dist/providers/cloudflare/safety.js +3 -3
- package/dist/providers/cloudflare/skill.generated.js +1 -1
- package/dist/providers/docs/lib/schemas.js +24 -22
- package/dist/providers/docs/safety.js +2 -0
- package/dist/providers/docs/skill.generated.js +1 -1
- package/dist/providers/docs/tools.js +4 -14
- package/dist/providers/drive/api.js +1 -0
- package/dist/providers/drive/lib/contracts.js +8 -5
- package/dist/providers/drive/skill.generated.js +1 -1
- package/dist/providers/drive/tools.js +23 -41
- package/dist/providers/forms/api.js +1 -0
- package/dist/providers/forms/skill.generated.js +1 -1
- package/dist/providers/forms/tools.js +11 -16
- package/dist/providers/github/api.js +1 -0
- package/dist/providers/github/lib/discovery.js +1 -1
- package/dist/providers/github/skill.generated.js +1 -1
- package/dist/providers/github/tools.js +61 -17
- package/dist/providers/gmail/lib/schemas.js +60 -50
- package/dist/providers/gmail/skill.generated.js +1 -1
- package/dist/providers/gmail/tools.js +12 -26
- package/dist/providers/granola/index.js +2 -0
- package/dist/providers/granola/skill.generated.js +1 -1
- package/dist/providers/overflow/api.js +8 -4
- package/dist/providers/overflow/index.js +2 -1
- package/dist/providers/overflow/skill.generated.js +1 -1
- package/dist/providers/overflow/tools.js +3 -7
- package/dist/providers/planning-center/api.js +5 -2
- package/dist/providers/planning-center/lib/constants.d.ts +1 -1
- package/dist/providers/planning-center/lib/json-api.js +1 -9
- package/dist/providers/planning-center/skill.generated.js +1 -1
- package/dist/providers/planning-center/tools.js +2 -1
- package/dist/providers/quickbooks/safety.js +80 -39
- package/dist/providers/quickbooks/skill.generated.js +1 -1
- package/dist/providers/sheets/api.js +1 -0
- package/dist/providers/sheets/skill.generated.js +1 -1
- package/dist/providers/sheets/tools.js +14 -23
- package/dist/providers/slides/skill.generated.js +1 -1
- package/dist/providers/slides/tools.js +19 -26
- package/dist/providers/stripe/index.js +2 -1
- package/dist/providers/stripe/safety.js +2 -2
- package/dist/providers/stripe/skill.generated.js +1 -1
- package/dist/providers/tithely/api.js +5 -8
- package/dist/providers/tithely/index.js +3 -2
- package/dist/providers/tithely/skill.generated.js +1 -1
- package/dist/providers/tithely/tools.js +7 -5
- package/dist/providers/vercel/api.js +2 -7
- package/dist/providers/vercel/lib/pin.js +1 -1
- package/dist/providers/vercel/safety.js +1 -1
- package/dist/providers/vercel/skill.generated.js +1 -1
- package/dist/providers/vercel/tools.js +6 -4
- package/dist/redaction-marker.js +2 -2
- package/dist/registry.d.ts +11 -5
- package/dist/registry.js +46 -20
- package/dist/request-principal.js +1 -1
- package/dist/request-state.js +2 -2
- package/dist/routes/access-tokens.js +1 -1
- package/dist/routes/activity-disclosure.js +1 -1
- package/dist/routes/connect.js +2 -2
- package/dist/routes/credentials.js +130 -61
- package/dist/routes/dispatch.js +5 -1
- package/dist/routes/mcp.js +34 -17
- package/dist/routes/oauth-management.js +1 -1
- package/dist/routes/operator.js +6 -5
- package/dist/routes/shared.d.ts +13 -8
- package/dist/routes/shared.js +15 -9
- package/dist/routes/ui-config.js +3 -3
- package/dist/routes/ui-data.js +126 -20
- package/dist/routes/ui.js +17 -1
- package/dist/runtime/call-admission.js +1 -1
- package/dist/runtime/call.js +8 -3
- package/dist/runtime/native-storage.js +9 -0
- package/dist/runtime/request-reads.js +182 -0
- package/dist/runtime/request-scope.js +16 -0
- package/dist/runtime/settlement.js +1 -1
- package/dist/runtime/shared-read.js +2 -15
- package/dist/runtime/storage.js +12 -0
- package/dist/sent-secrets.d.ts +1 -2
- package/dist/sent-secrets.js +3 -8
- package/dist/server.js +4 -4
- package/dist/skills.js +44 -54
- package/dist/sqlite.d.ts +0 -19
- package/dist/sqlite.js +2 -83
- package/dist/storage/keys.js +69 -128
- package/dist/storage/memory.js +8 -2
- package/dist/storage/sql-native.js +27 -101
- package/dist/storage/sql.d.ts +1 -24
- package/dist/storage/sql.js +13 -14
- package/dist/storage-digest.js +32 -0
- package/dist/timeout.js +8 -0
- package/dist/tool-error.js +4 -0
- package/dist/types.d.ts +33 -0
- package/dist/url-safety.js +0 -4
- package/dist/usage-guide.js +18 -37
- package/dist/validate.d.ts +3 -0
- package/dist/validate.js +220 -158
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/worker.js +1 -1
- package/dist/write-outcome.js +0 -3
- package/documentation/README.md +19 -55
- package/documentation/architecture.md +436 -515
- package/documentation/auth.md +229 -88
- package/documentation/code-mode.md +201 -94
- package/documentation/deploying.md +25 -43
- package/documentation/integrating.md +18 -19
- package/documentation/meta-tools.md +225 -51
- package/documentation/migration-0.33.md +10 -6
- package/documentation/migration-0.34.md +5 -5
- package/documentation/migration-retired-layouts.md +74 -0
- package/documentation/operator-tests.md +20 -120
- package/documentation/operator-ui.md +108 -16
- package/documentation/pool-membership-upgrade.md +44 -0
- package/documentation/provider-auth-migration.md +4 -10
- package/documentation/provider-migration-0.29.md +4 -15
- package/documentation/providers.md +105 -0
- package/examples/worker/AGENTS.md +14 -16
- package/examples/worker/README.md +24 -118
- package/examples/worker/src/connecta.config.ts +1 -0
- package/guides/connecting-agents.md +9 -31
- package/guides/how-it-works.md +6 -4
- package/guides/safety-and-trust.md +4 -1
- package/package.json +19 -19
- package/templates/node/AGENTS.md +13 -12
- package/templates/node/README.md +9 -26
- package/templates/node/package.json +1 -1
- package/templates/node/src/connecta.config.ts +1 -0
- package/dist/auth/oauth-v2-migration.js +0 -135
- package/dist/provider-call-facts.d.ts +0 -115
- package/dist/provider-call.d.ts +0 -112
- package/dist/storage/d1-copy.js +0 -255
- package/dist/storage/sql-schema.d.ts +0 -6
- package/examples/worker/kv-to-d1.wrangler.jsonc +0 -26
- package/examples/worker/scripts/copy-kv-to-d1.mjs +0 -162
|
@@ -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. `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
|
|
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.",
|
|
4
4
|
"instructionsHeading": "Account instructions"
|
|
5
5
|
};
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
// Effect declarations are the catalog and runtime decoder for every named Docs tool.
|
|
2
|
+
import { routeDescription } from "../../../agent-routes.js";
|
|
2
3
|
import { Schema } from "effect";
|
|
3
4
|
import { Integer, isMinChars, isMaxChars } from "../../../http/json-schema.js";
|
|
4
|
-
import {
|
|
5
|
+
import { fieldsContract } from "../../../http/fields.js";
|
|
5
6
|
import { REQUEST_KINDS } from "./requests.js";
|
|
6
7
|
const matches = (pattern) => Schema.makeFilter((value) => new RegExp(pattern).test(value), {
|
|
7
8
|
expected: `a string matching ${pattern}`,
|
|
@@ -12,27 +13,6 @@ const Request = Schema.Record(Schema.String, Schema.Record(Schema.String, Schema
|
|
|
12
13
|
expected: "exactly one reviewed Docs request kind",
|
|
13
14
|
toJsonSchema: () => ({ minProperties: 1, maxProperties: 1, propertyNames: { enum: [...REQUEST_KINDS] } }),
|
|
14
15
|
}));
|
|
15
|
-
export const GET_DOCUMENT_INPUT = Schema.Struct({
|
|
16
|
-
"documentId": Schema.String.check(isMinChars(1), isMaxChars(256), matches("^[A-Za-z0-9_-]+$")).annotate({
|
|
17
|
-
description: "Document id: the part after /d/ in its URL, or a Drive file id for a Google Doc.",
|
|
18
|
-
}),
|
|
19
|
-
"tabId": Schema.optionalKey(Schema.String.check(isMinChars(1), isMaxChars(128), matches("^[A-Za-z0-9._-]+$")).annotate({
|
|
20
|
-
description: "Render only this tab (from a previous get_document); omit for every tab.",
|
|
21
|
-
})),
|
|
22
|
-
"maxChars": Schema.optionalKey(Integer.check(Schema.isGreaterThanOrEqualTo(0), Schema.isLessThanOrEqualTo(1000000)).annotate({
|
|
23
|
-
description: "Characters of text kept across all rendered tabs, 0 to 1000000; defaults to 20000. Connecta's cap. Longer text ends with a truncation marker. Above the default, call directly (call_tool, connecta.result to page): execute_code carries at most 256 KiB per result.",
|
|
24
|
-
})),
|
|
25
|
-
"withIndexes": Schema.optionalKey(Schema.Boolean.annotate({
|
|
26
|
-
description: "Add each tab's elements: every paragraph, table, and table of contents with UTF-16 startIndex and endIndex, 64 KiB per page (connecta's cap); page with cursor.",
|
|
27
|
-
})),
|
|
28
|
-
"cursor": Schema.optionalKey(Schema.String.check(isMinChars(1)).annotate({
|
|
29
|
-
description: "Opaque page.nextCursor from the previous indexed read; keep the same arguments and document revision.",
|
|
30
|
-
})),
|
|
31
|
-
"suggestions": Schema.optionalKey(Schema.Literals(["inline", "accepted", "rejected"]).annotate({
|
|
32
|
-
description: "How suggested edits read: inline (editors' default; the indexes edits use), or a preview with all accepted or all rejected (indexes not valid for edits).",
|
|
33
|
-
})),
|
|
34
|
-
fields: Schema.optionalKey(fieldsInput("Dot paths to keep in the result. Page and truncation metadata are always returned.")),
|
|
35
|
-
});
|
|
36
16
|
export const GET_DOCUMENT_OUTPUT = Schema.Struct({
|
|
37
17
|
"documentId": Schema.optionalKey(Schema.String),
|
|
38
18
|
"title": Schema.optionalKey(Schema.String),
|
|
@@ -60,6 +40,28 @@ export const GET_DOCUMENT_OUTPUT = Schema.Struct({
|
|
|
60
40
|
"dropped": Schema.optionalKey(Schema.Array(Schema.String)),
|
|
61
41
|
"truncated": Schema.optionalKey(Schema.Array(Schema.String)),
|
|
62
42
|
});
|
|
43
|
+
export const GET_DOCUMENT_FIELDS = fieldsContract("result", GET_DOCUMENT_OUTPUT, "Page and truncation metadata are always returned.");
|
|
44
|
+
export const GET_DOCUMENT_INPUT = Schema.Struct({
|
|
45
|
+
"documentId": Schema.String.check(isMinChars(1), isMaxChars(256), matches("^[A-Za-z0-9_-]+$")).annotate({
|
|
46
|
+
description: "Document id: the part after /d/ in its URL, or a Drive file id for a Google Doc.",
|
|
47
|
+
}),
|
|
48
|
+
"tabId": Schema.optionalKey(Schema.String.check(isMinChars(1), isMaxChars(128), matches("^[A-Za-z0-9._-]+$")).annotate({
|
|
49
|
+
description: "Render only this tab (from a previous get_document); omit for every tab.",
|
|
50
|
+
})),
|
|
51
|
+
"maxChars": Schema.optionalKey(Integer.check(Schema.isGreaterThanOrEqualTo(0), Schema.isLessThanOrEqualTo(1000000)).annotate({
|
|
52
|
+
description: routeDescription((phrases) => `Characters of text kept across all rendered tabs, 0 to 1000000; defaults to 20000. Connecta's cap. Longer text ends with a truncation marker. Above the default, ${phrases.callDirectlyAndPage}${phrases.program ? `: ${phrases.program} carries at most 256 KiB per result` : ""}.`),
|
|
53
|
+
})),
|
|
54
|
+
"withIndexes": Schema.optionalKey(Schema.Boolean.annotate({
|
|
55
|
+
description: "Add each tab's elements: every paragraph, table, and table of contents with UTF-16 startIndex and endIndex, 64 KiB per page (connecta's cap); page with cursor.",
|
|
56
|
+
})),
|
|
57
|
+
"cursor": Schema.optionalKey(Schema.String.check(isMinChars(1)).annotate({
|
|
58
|
+
description: "Opaque page.nextCursor from the previous indexed read; keep the same arguments and document revision.",
|
|
59
|
+
})),
|
|
60
|
+
"suggestions": Schema.optionalKey(Schema.Literals(["inline", "accepted", "rejected"]).annotate({
|
|
61
|
+
description: "How suggested edits read: inline (editors' default; the indexes edits use), or a preview with all accepted or all rejected (indexes not valid for edits).",
|
|
62
|
+
})),
|
|
63
|
+
fields: Schema.optionalKey(GET_DOCUMENT_FIELDS.input),
|
|
64
|
+
});
|
|
63
65
|
export const CREATE_DOCUMENT_INPUT = Schema.Struct({
|
|
64
66
|
"title": Schema.String.check(isMinChars(1), isMaxChars(1000), matches("^[^\\r\\n]*$")).annotate({
|
|
65
67
|
description: "Document title, one line. Connecta's 1,000-character cap.",
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Effect } from "effect";
|
|
2
2
|
import { RequestSecrets } from "../../http/services.js";
|
|
3
3
|
import { googleSafety } from "../_shared/google/kit.js";
|
|
4
|
+
import { pathWithholds } from "../_shared/rest/value-safety.js";
|
|
4
5
|
/** ImageProperties.contentUri delegates the requester's image access for 30 minutes.
|
|
5
6
|
* Keep ordinary authored links/sourceUri, but withhold that vendor capability on
|
|
6
7
|
* every document response, including tabs, headers and suggested image changes.
|
|
@@ -29,6 +30,7 @@ export function docsSafety(operations) {
|
|
|
29
30
|
};
|
|
30
31
|
return visit(data);
|
|
31
32
|
},
|
|
33
|
+
withholds: () => pathWithholds({ paths: ["imageProperties.contentUri"], anyDepth: true }),
|
|
32
34
|
},
|
|
33
35
|
};
|
|
34
36
|
}
|
|
@@ -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. `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/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.",
|
|
4
4
|
"instructionsHeading": "Connection instructions"
|
|
5
5
|
};
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
+
import { asRecord, asArray, compact } from "../_shared/json.js";
|
|
1
2
|
// Google Docs named projections and bounded writes over the provider kit.
|
|
2
3
|
import { Effect, Option } from "effect";
|
|
3
4
|
import { canonicalJson } from "../../canonical-json.js";
|
|
4
5
|
import { ConnectorCallError } from "../../errors.js";
|
|
5
6
|
import { Refusal } from "../../http/errors.js";
|
|
6
|
-
import {
|
|
7
|
+
import { selectFields } from "../../http/fields.js";
|
|
7
8
|
import { Recovery } from "../../http/services.js";
|
|
8
9
|
import { requestSentSecrets, redactBeforeCut } from "../../sent-secrets.js";
|
|
9
10
|
import { providerCallFailure } from "../../write-outcome.js";
|
|
@@ -28,15 +29,6 @@ const DEFAULT_MAX_CHARS = 20_000;
|
|
|
28
29
|
const ELEMENT_PAGE_BYTES = 64 * 1024;
|
|
29
30
|
/** Characters of each index row's text preview. */
|
|
30
31
|
const PREVIEW_CHARS = 80;
|
|
31
|
-
function asRecord(value) {
|
|
32
|
-
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
33
|
-
}
|
|
34
|
-
function asArray(value) {
|
|
35
|
-
return Array.isArray(value) ? value : [];
|
|
36
|
-
}
|
|
37
|
-
function compact(value) {
|
|
38
|
-
return Object.fromEntries(Object.entries(value).filter(([, entry]) => entry !== undefined));
|
|
39
|
-
}
|
|
40
32
|
function text(value) {
|
|
41
33
|
return typeof value === "string" && value !== "" ? value : undefined;
|
|
42
34
|
}
|
|
@@ -368,7 +360,7 @@ function batchPath(documentId) {
|
|
|
368
360
|
function revisionAfter(reply, dropped) {
|
|
369
361
|
return keptId(asRecord(reply["writeControl"])["requiredRevisionId"], "revisionId", "revisionId", dropped);
|
|
370
362
|
}
|
|
371
|
-
// No cause: what it would carry is a transport, stream, or parser error that can quote the reply
|
|
363
|
+
// No cause: what it would carry is a transport, stream, or parser error that can quote the reply.
|
|
372
364
|
function uncertain(message, ctx) {
|
|
373
365
|
return providerCallFailure(ctx, { title: "Google Docs" }, new ConnectorCallError("connector_call_failed", message, { retryable: false }), {
|
|
374
366
|
dispatched: true,
|
|
@@ -523,9 +515,7 @@ export function docsTools({ connection }) {
|
|
|
523
515
|
...(tool.writeSteps ? { writeSteps: tool.writeSteps } : {}),
|
|
524
516
|
run: (args) => Effect.gen(function* () {
|
|
525
517
|
const fields = isRead ? args["fields"] : undefined;
|
|
526
|
-
const problem = isRead
|
|
527
|
-
? fieldsProblem(fields, new Set(Object.keys(schemas.GET_DOCUMENT_OUTPUT.fields)), "the result")
|
|
528
|
-
: undefined;
|
|
518
|
+
const problem = isRead ? schemas.GET_DOCUMENT_FIELDS.problem(fields) : undefined;
|
|
529
519
|
if (problem)
|
|
530
520
|
return yield* Effect.fail(new Refusal({ error: problem }));
|
|
531
521
|
const operation = yield* Effect.serviceOption(Operation);
|
|
@@ -66,6 +66,7 @@ export const safety = {
|
|
|
66
66
|
return { method: op.method, template: op.path };
|
|
67
67
|
},
|
|
68
68
|
redact: (data, op) => withholdDriveLinks(values.redact(data, op.method, op.template)),
|
|
69
|
+
withholds: (op) => values.withholds(op.method, op.template),
|
|
69
70
|
withholdsErrors: (op) => values.withholdsErrors(driveIndex.ops.find((entry) => entry.method === op.method && entry.path === op.template)),
|
|
70
71
|
},
|
|
71
72
|
sensitiveFields: { names: [], choice: "drop" },
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
// Drive named tool contracts, kept separate from the workflow programs.
|
|
2
|
+
import { routeDescription } from "../../../agent-routes.js";
|
|
2
3
|
import { Schema } from "effect";
|
|
3
4
|
import { Integer, isMinChars, isMaxChars } from "../../../http/json-schema.js";
|
|
4
5
|
const matchesPattern = (pattern) => Schema.makeFilter((value) => pattern.test(value), {
|
|
5
6
|
expected: `a string matching ${pattern.source}`,
|
|
6
7
|
toJsonSchema: () => ({ pattern: pattern.source }),
|
|
7
8
|
});
|
|
9
|
+
/** maxBytes on every read: a program receives the default; only a direct call raises it. */
|
|
10
|
+
const MAX_BYTES_DESCRIPTION = routeDescription((phrases) => `Serialized bytes of the whole result; defaults to 196608${phrases.program ? `, what ${phrases.program} can receive` : ""}. Raise it, to 4194304, ${phrases.onlyDirectRead}.`);
|
|
8
11
|
export const search_files = {
|
|
9
12
|
input: Schema.Struct({
|
|
10
13
|
"query": Schema.optionalKey(Schema.String.check(isMaxChars(2048)).annotate({
|
|
@@ -35,7 +38,7 @@ export const search_files = {
|
|
|
35
38
|
"description": "Opaque page.nextCursor from the previous page. Pass it back unchanged with the same arguments.",
|
|
36
39
|
})),
|
|
37
40
|
"maxBytes": Schema.optionalKey(Integer.check(Schema.isGreaterThanOrEqualTo(65536), Schema.isLessThanOrEqualTo(4194304)).annotate({
|
|
38
|
-
"description":
|
|
41
|
+
"description": MAX_BYTES_DESCRIPTION,
|
|
39
42
|
})),
|
|
40
43
|
}),
|
|
41
44
|
output: Schema.Struct({
|
|
@@ -81,7 +84,7 @@ export const list_folder_items = {
|
|
|
81
84
|
"description": "Opaque page.nextCursor from the previous page. Pass it back unchanged with the same arguments.",
|
|
82
85
|
})),
|
|
83
86
|
"maxBytes": Schema.optionalKey(Integer.check(Schema.isGreaterThanOrEqualTo(65536), Schema.isLessThanOrEqualTo(4194304)).annotate({
|
|
84
|
-
"description":
|
|
87
|
+
"description": MAX_BYTES_DESCRIPTION,
|
|
85
88
|
})),
|
|
86
89
|
}),
|
|
87
90
|
output: Schema.Struct({
|
|
@@ -150,7 +153,7 @@ export const get_file_content = {
|
|
|
150
153
|
"description": "Characters (code points) of text kept, 1 to 100000; defaults to 20000. Longer text ends with a truncation marker. Binaries ignore it.",
|
|
151
154
|
})),
|
|
152
155
|
"maxBytes": Schema.optionalKey(Integer.check(Schema.isGreaterThanOrEqualTo(65536), Schema.isLessThanOrEqualTo(4194304)).annotate({
|
|
153
|
-
"description":
|
|
156
|
+
"description": MAX_BYTES_DESCRIPTION,
|
|
154
157
|
})),
|
|
155
158
|
}),
|
|
156
159
|
output: Schema.Struct({
|
|
@@ -177,7 +180,7 @@ export const list_permissions = {
|
|
|
177
180
|
"description": "Opaque page.nextCursor from the previous page. Pass it back unchanged with the same arguments.",
|
|
178
181
|
})),
|
|
179
182
|
"maxBytes": Schema.optionalKey(Integer.check(Schema.isGreaterThanOrEqualTo(65536), Schema.isLessThanOrEqualTo(4194304)).annotate({
|
|
180
|
-
"description":
|
|
183
|
+
"description": MAX_BYTES_DESCRIPTION,
|
|
181
184
|
})),
|
|
182
185
|
}),
|
|
183
186
|
output: Schema.Struct({
|
|
@@ -214,7 +217,7 @@ export const list_shared_drives = {
|
|
|
214
217
|
"description": "Opaque page.nextCursor from the previous page. Pass it back unchanged with the same arguments.",
|
|
215
218
|
})),
|
|
216
219
|
"maxBytes": Schema.optionalKey(Integer.check(Schema.isGreaterThanOrEqualTo(65536), Schema.isLessThanOrEqualTo(4194304)).annotate({
|
|
217
|
-
"description":
|
|
220
|
+
"description": MAX_BYTES_DESCRIPTION,
|
|
218
221
|
})),
|
|
219
222
|
}),
|
|
220
223
|
output: Schema.Struct({
|
|
@@ -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. `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
|
|
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.",
|
|
4
4
|
"instructionsHeading": "Connection instructions"
|
|
5
5
|
};
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import { routePhrases } from "../../agent-routes.js";
|
|
2
|
+
import { asRecord, asArray, compact } from "../_shared/json.js";
|
|
3
|
+
import { decodeBase64, decodeBase64url } from "../../bytes.js";
|
|
1
4
|
// Google Drive named workflows, bounded projections and bound continuation on the provider kit.
|
|
2
5
|
import { Effect, Option, Schema } from "effect";
|
|
3
6
|
import { ConnectorCallError } from "../../errors.js";
|
|
@@ -6,7 +9,7 @@ import { Classification, Dispatches, Signal } from "../../http/services.js";
|
|
|
6
9
|
import { RESULT_BUDGET_BYTES, clampText, jsonBytes } from "../_shared/google/result-size.js";
|
|
7
10
|
import { googleOutcomeOf, googleFailureMessage } from "../_shared/google/kit.js";
|
|
8
11
|
import { read, write } from "../../http/tools.js";
|
|
9
|
-
import {
|
|
12
|
+
import { fieldsContract, selectFields } from "../../http/fields.js";
|
|
10
13
|
import { catalogSchema } from "../../http/json-schema.js";
|
|
11
14
|
import { Refusal } from "../../http/errors.js";
|
|
12
15
|
import { Operation } from "../../runtime/call.js";
|
|
@@ -83,15 +86,6 @@ const DETAIL_FIELDS = `${SUMMARY_FIELDS},description,createdTime,lastModifyingUs
|
|
|
83
86
|
const PERMISSION_FIELDS = "id,type,role,emailAddress,domain,displayName,allowFileDiscovery,expirationTime,deleted,permissionDetails(inherited,inheritedFrom)";
|
|
84
87
|
/** Every file request reaches shared drives as well as My Drive. */
|
|
85
88
|
const ALL_DRIVES = { supportsAllDrives: true };
|
|
86
|
-
function asRecord(value) {
|
|
87
|
-
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
88
|
-
}
|
|
89
|
-
function asArray(value) {
|
|
90
|
-
return Array.isArray(value) ? value : [];
|
|
91
|
-
}
|
|
92
|
-
function compact(value) {
|
|
93
|
-
return Object.fromEntries(Object.entries(value).filter(([, entry]) => entry !== undefined));
|
|
94
|
-
}
|
|
95
89
|
function text(value) {
|
|
96
90
|
return typeof value === "string" && value !== "" ? value : undefined;
|
|
97
91
|
}
|
|
@@ -260,8 +254,7 @@ function encodeCursor(ctx, resume) {
|
|
|
260
254
|
const FINGERPRINT = /^[A-Za-z0-9_-]{16}$/;
|
|
261
255
|
function decodeCursor(cursor) {
|
|
262
256
|
try {
|
|
263
|
-
const
|
|
264
|
-
const json = new TextDecoder().decode(fromBase64(base + "=".repeat((4 - (base.length % 4)) % 4)));
|
|
257
|
+
const json = new TextDecoder().decode(decodeBase64url(cursor, true));
|
|
265
258
|
const [token, skip, size, scope, page, last] = JSON.parse(json);
|
|
266
259
|
const midPage = typeof page === "string" && FINGERPRINT.test(page) && typeof last === "string" && FINGERPRINT.test(last);
|
|
267
260
|
if ((token === null || (typeof token === "string" && token !== "")) &&
|
|
@@ -440,7 +433,7 @@ function capped(ctx, whole, max, partial, fits) {
|
|
|
440
433
|
if (fits(byChars))
|
|
441
434
|
return { content: byChars, contentTruncated: true };
|
|
442
435
|
// The bytes bind first: the most characters that fit, by binary search.
|
|
443
|
-
const raise = `maxBytes (up to ${MAX_RESULT_BYTES},
|
|
436
|
+
const raise = `maxBytes (up to ${MAX_RESULT_BYTES}, ${routePhrases(ctx).directOnly})`;
|
|
444
437
|
let low = 0;
|
|
445
438
|
let high = Math.min(total, max);
|
|
446
439
|
while (low < high) {
|
|
@@ -471,19 +464,6 @@ function base64(bytes) {
|
|
|
471
464
|
}
|
|
472
465
|
return btoa(binary);
|
|
473
466
|
}
|
|
474
|
-
function fromBase64(value) {
|
|
475
|
-
let binary;
|
|
476
|
-
try {
|
|
477
|
-
binary = atob(value);
|
|
478
|
-
}
|
|
479
|
-
catch {
|
|
480
|
-
throw new ConnectorCallError("invalid_args", "contentBase64 is not valid base64.");
|
|
481
|
-
}
|
|
482
|
-
const bytes = new Uint8Array(binary.length);
|
|
483
|
-
for (let index = 0; index < binary.length; index += 1)
|
|
484
|
-
bytes[index] = binary.charCodeAt(index);
|
|
485
|
-
return bytes;
|
|
486
|
-
}
|
|
487
467
|
/**
|
|
488
468
|
* Why the content tool returns no content for a file, as the note says it.
|
|
489
469
|
* Only a well-formed id or media type is interpolated; anything else Drive
|
|
@@ -607,7 +587,7 @@ function readContent(client, ctx, fileId, maxChars, maxBytes) {
|
|
|
607
587
|
format: "unavailable",
|
|
608
588
|
contentTruncated: false,
|
|
609
589
|
note: limit < BINARY_INLINE_BYTES
|
|
610
|
-
? `A binary file of ${bytes} does not fit this result's maxBytes (${maxBytes}), which holds ${limit} bytes of file; no content is returned. Raise maxBytes, up to ${MAX_RESULT_BYTES}
|
|
590
|
+
? `A binary file of ${bytes} does not fit this result's maxBytes (${maxBytes}), which holds ${limit} bytes of file; no content is returned. Raise maxBytes, up to ${MAX_RESULT_BYTES} ${routePhrases(ctx).directOnly}, to read up to ${BINARY_INLINE_BYTES} bytes, or open webViewLink.`
|
|
611
591
|
: `A binary file of ${bytes} is past this connection's ${BINARY_INLINE_BYTES}-byte inline cap; no content is returned. Open webViewLink.`,
|
|
612
592
|
});
|
|
613
593
|
// Metadata that already says too large saves the download; metadata that
|
|
@@ -681,7 +661,7 @@ function media(args) {
|
|
|
681
661
|
};
|
|
682
662
|
}
|
|
683
663
|
if (hasBinary) {
|
|
684
|
-
const bytes =
|
|
664
|
+
const bytes = contentBytes(args["contentBase64"]);
|
|
685
665
|
if (bytes.length > BINARY_INLINE_BYTES) {
|
|
686
666
|
throw new ConnectorCallError("invalid_args", `contentBase64 decodes to ${bytes.length} bytes, past this connection's ${BINARY_INLINE_BYTES}-byte upload cap.`);
|
|
687
667
|
}
|
|
@@ -1133,7 +1113,7 @@ function guarded(tool) {
|
|
|
1133
1113
|
return result;
|
|
1134
1114
|
const raisable = "maxBytes" in tool.contract.input.fields;
|
|
1135
1115
|
throw new ConnectorCallError("connector_call_failed", tool.annotations.readOnlyHint
|
|
1136
|
-
? `This ${tool.name} result serializes to ${bytes} bytes, past ${raisable ? `this call's maxBytes of ${budget}` : `the ${budget} bytes one result may carry`}; nothing is returned. ${raisable ? `Raise maxBytes (up to ${MAX_RESULT_BYTES},
|
|
1116
|
+
? `This ${tool.name} result serializes to ${bytes} bytes, past ${raisable ? `this call's maxBytes of ${budget}` : `the ${budget} bytes one result may carry`}; nothing is returned. ${raisable ? `Raise maxBytes (up to ${MAX_RESULT_BYTES}, ${routePhrases(ctx).directOnly}), or narrow the request.` : "Read the item in Drive instead."}`
|
|
1137
1117
|
: `${tool.name} applied, but its result serializes to ${bytes} bytes, past the ${budget} bytes one result may carry, so it is not returned. Re-read the item with get_file; do not repeat the write.`, { retryable: false });
|
|
1138
1118
|
}),
|
|
1139
1119
|
};
|
|
@@ -1143,16 +1123,10 @@ function buildTool(connection, tool) {
|
|
|
1143
1123
|
const isList = ["search_files", "list_folder_items", "list_permissions", "list_shared_drives"].includes(tool.name);
|
|
1144
1124
|
const output = asRecord(catalogSchema(tool.contract.output, "output"));
|
|
1145
1125
|
const row = isList ? asRecord(asRecord(asRecord(output["properties"])["items"])["items"]) : output;
|
|
1146
|
-
const
|
|
1126
|
+
const fields = isRead ? fieldsContract(isList ? "item" : "result", row) : undefined;
|
|
1147
1127
|
const input = Schema.Struct({
|
|
1148
1128
|
...tool.contract.input.fields,
|
|
1149
|
-
...(
|
|
1150
|
-
? {
|
|
1151
|
-
fields: Schema.optionalKey(fieldsInput(isList
|
|
1152
|
-
? "Dot paths to keep in each item; only narrows the result."
|
|
1153
|
-
: "Dot paths to keep in the result; only narrows the result.")),
|
|
1154
|
-
}
|
|
1155
|
-
: {}),
|
|
1129
|
+
...(fields ? { fields: Schema.optionalKey(fields.input) } : {}),
|
|
1156
1130
|
});
|
|
1157
1131
|
const spec = {
|
|
1158
1132
|
name: tool.name,
|
|
@@ -1161,8 +1135,8 @@ function buildTool(connection, tool) {
|
|
|
1161
1135
|
output: tool.contract.output,
|
|
1162
1136
|
...(tool.writeSteps ? { writeSteps: tool.writeSteps } : {}),
|
|
1163
1137
|
run: (args) => Effect.gen(function* () {
|
|
1164
|
-
const
|
|
1165
|
-
const problem =
|
|
1138
|
+
const selected = args["fields"];
|
|
1139
|
+
const problem = fields?.problem(selected);
|
|
1166
1140
|
if (problem)
|
|
1167
1141
|
return yield* Effect.fail(new Refusal({ error: problem }));
|
|
1168
1142
|
const active = yield* Effect.serviceOption(Operation);
|
|
@@ -1182,9 +1156,9 @@ function buildTool(connection, tool) {
|
|
|
1182
1156
|
? isList
|
|
1183
1157
|
? {
|
|
1184
1158
|
...result,
|
|
1185
|
-
items: asArray(result["items"]).map((item) => retainTruncated(item, selectFields(item,
|
|
1159
|
+
items: asArray(result["items"]).map((item) => retainTruncated(item, selectFields(item, selected))),
|
|
1186
1160
|
}
|
|
1187
|
-
: retainTruncated(result, selectFields(result,
|
|
1161
|
+
: retainTruncated(result, selectFields(result, selected))
|
|
1188
1162
|
: result;
|
|
1189
1163
|
}).pipe(Effect.catchDefect((error) => error instanceof ConnectorCallError ? Effect.fail(new Refusal({ error })) : Effect.die(error))),
|
|
1190
1164
|
};
|
|
@@ -1288,3 +1262,11 @@ function decisionInputs(file, keys) {
|
|
|
1288
1262
|
}
|
|
1289
1263
|
}
|
|
1290
1264
|
}
|
|
1265
|
+
function contentBytes(value) {
|
|
1266
|
+
try {
|
|
1267
|
+
return decodeBase64(value);
|
|
1268
|
+
}
|
|
1269
|
+
catch {
|
|
1270
|
+
throw new ConnectorCallError("invalid_args", "contentBase64 is not valid base64.");
|
|
1271
|
+
}
|
|
1272
|
+
}
|
|
@@ -55,6 +55,7 @@ export const safety = {
|
|
|
55
55
|
return { method: op.method, template: op.path };
|
|
56
56
|
},
|
|
57
57
|
redact: (data, op) => values.redact(data, op.method, op.template),
|
|
58
|
+
withholds: (op) => values.withholds(op.method, op.template),
|
|
58
59
|
withholdsErrors: (op) => values.withholdsErrors(formsIndex.ops.find((entry) => entry.method === op.method && entry.path === op.template)),
|
|
59
60
|
},
|
|
60
61
|
sensitiveFields: { names: [], choice: "drop" },
|
|
@@ -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. `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
|
|
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.",
|
|
4
4
|
"instructionsHeading": "Connection instructions"
|
|
5
5
|
};
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { asRecord, asArray, compact } from "../_shared/json.js";
|
|
1
2
|
// Forms projections and revision-checked workflows on the standard provider kit.
|
|
2
3
|
import { Effect, Option, Schema } from "effect";
|
|
3
4
|
import { ConnectorCallError } from "../../errors.js";
|
|
@@ -7,7 +8,7 @@ import { encodeForAgent, redactBeforeCut, redactSentSecrets } from "../../sent-s
|
|
|
7
8
|
import { Integer, isMaxChars, isMinChars } from "../../http/json-schema.js";
|
|
8
9
|
import { boundCursor, StandardPage } from "../../http/paging.js";
|
|
9
10
|
import { read, write } from "../../http/tools.js";
|
|
10
|
-
import {
|
|
11
|
+
import { fieldsContract, selectFields } from "../../http/fields.js";
|
|
11
12
|
import { Refusal } from "../../http/errors.js";
|
|
12
13
|
import { Operation } from "../../runtime/call.js";
|
|
13
14
|
import { formsIndex, FORMS_REQUEST_KINDS } from "./lib/hatch.js";
|
|
@@ -40,15 +41,6 @@ const MAX_REQUESTS = 100;
|
|
|
40
41
|
* cursor included, is measured before it is returned.
|
|
41
42
|
*/
|
|
42
43
|
const PAGE_RESERVE_BYTES = 4 * 1024;
|
|
43
|
-
function asRecord(value) {
|
|
44
|
-
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
45
|
-
}
|
|
46
|
-
function asArray(value) {
|
|
47
|
-
return Array.isArray(value) ? value : [];
|
|
48
|
-
}
|
|
49
|
-
function compact(value) {
|
|
50
|
-
return Object.fromEntries(Object.entries(value).filter(([, entry]) => entry !== undefined));
|
|
51
|
-
}
|
|
52
44
|
function text(value) {
|
|
53
45
|
return typeof value === "string" && value !== "" ? value : undefined;
|
|
54
46
|
}
|
|
@@ -559,6 +551,9 @@ const BatchRecord = optional({
|
|
|
559
551
|
replyCount: Integer,
|
|
560
552
|
createdItemCount: Integer,
|
|
561
553
|
});
|
|
554
|
+
const FORM_FIELDS = fieldsContract("result", FormRecord, "Page and cut metadata are preserved.");
|
|
555
|
+
const RESPONSE_FIELDS = fieldsContract("result", ResponseRecord, "Cut and recovery metadata are preserved.");
|
|
556
|
+
const RESPONSE_ITEM_FIELDS = fieldsContract("item", ResponseRecord, "It only narrows safe data.");
|
|
562
557
|
const contextOf = Effect.gen(function* () {
|
|
563
558
|
const operation = yield* Effect.serviceOption(Operation);
|
|
564
559
|
if (Option.isNone(operation) || !operation.value)
|
|
@@ -617,12 +612,12 @@ export function formsTools({ connection }) {
|
|
|
617
612
|
input: Schema.Struct({
|
|
618
613
|
formId,
|
|
619
614
|
cursor,
|
|
620
|
-
fields: Schema.optionalKey(
|
|
615
|
+
fields: Schema.optionalKey(FORM_FIELDS.input),
|
|
621
616
|
}),
|
|
622
617
|
output: FormRecord,
|
|
623
618
|
run: (args) => Effect.gen(function* () {
|
|
624
619
|
const ctx = yield* contextOf;
|
|
625
|
-
const problem =
|
|
620
|
+
const problem = FORM_FIELDS.problem(args.fields);
|
|
626
621
|
if (problem)
|
|
627
622
|
return yield* Effect.fail(new Refusal({ error: problem }));
|
|
628
623
|
const formId = args["formId"];
|
|
@@ -772,7 +767,7 @@ export function formsTools({ connection }) {
|
|
|
772
767
|
name: "list_responses",
|
|
773
768
|
description: "List a Google Form's submitted responses, each answer labeled with its question's title in form order. Long answers are cut; get_response reads one whole.",
|
|
774
769
|
input: Schema.Struct({
|
|
775
|
-
fields: Schema.optionalKey(
|
|
770
|
+
fields: Schema.optionalKey(RESPONSE_ITEM_FIELDS.input),
|
|
776
771
|
formId,
|
|
777
772
|
submittedAfter: Schema.optionalKey(Schema.String.check(pattern("^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(\\.\\d{1,9})?Z$")).annotate({
|
|
778
773
|
description: "Exclusive submitted-after UTC instant, e.g. 2026-10-01T00:00:00Z.",
|
|
@@ -791,7 +786,7 @@ export function formsTools({ connection }) {
|
|
|
791
786
|
writeSteps: pairedReads,
|
|
792
787
|
run: (args) => Effect.gen(function* () {
|
|
793
788
|
const ctx = yield* contextOf;
|
|
794
|
-
const problem =
|
|
789
|
+
const problem = RESPONSE_ITEM_FIELDS.problem(args.fields);
|
|
795
790
|
if (problem)
|
|
796
791
|
return yield* Effect.fail(new Refusal({ error: problem }));
|
|
797
792
|
const formId = args["formId"];
|
|
@@ -926,13 +921,13 @@ export function formsTools({ connection }) {
|
|
|
926
921
|
questionIds: Schema.optionalKey(Schema.Array(id("Question id from get_form or omittedQuestionIds."))
|
|
927
922
|
.check(Schema.isMinLength(1), Schema.isMaxLength(MAX_QUESTION_IDS), Schema.isUnique())
|
|
928
923
|
.annotate({ description: "Return only these questions for a large response." })),
|
|
929
|
-
fields: Schema.optionalKey(
|
|
924
|
+
fields: Schema.optionalKey(RESPONSE_FIELDS.input),
|
|
930
925
|
}),
|
|
931
926
|
output: ResponseRecord,
|
|
932
927
|
writeSteps: pairedReads,
|
|
933
928
|
run: (args) => Effect.gen(function* () {
|
|
934
929
|
const ctx = yield* contextOf;
|
|
935
|
-
const problem =
|
|
930
|
+
const problem = RESPONSE_FIELDS.problem(args.fields);
|
|
936
931
|
if (problem)
|
|
937
932
|
return yield* Effect.fail(new Refusal({ error: problem }));
|
|
938
933
|
const formId = args["formId"];
|
|
@@ -112,6 +112,7 @@ export function githubConnection() {
|
|
|
112
112
|
return { method: op.method, template: op.path };
|
|
113
113
|
},
|
|
114
114
|
redact: (data, op) => githubSafety.redact(data, op.method, op.template),
|
|
115
|
+
withholds: (op) => githubSafety.withholds(op.method, op.template),
|
|
115
116
|
withholdsErrors: () => true,
|
|
116
117
|
},
|
|
117
118
|
sensitiveFields: { names: [], choice: "drop" },
|
|
@@ -42,7 +42,7 @@ class RepositoryIndex extends OperationIndex {
|
|
|
42
42
|
if (method === "GET" &&
|
|
43
43
|
error instanceof ConnectorCallError &&
|
|
44
44
|
error.code === "invalid_args" &&
|
|
45
|
-
error.message.includes(" operation in the pinned API index ") &&
|
|
45
|
+
error.message.includes(" operation at that path in the pinned API index ") &&
|
|
46
46
|
/^\/repos\/[^/]+\/[^/]+\/(?:commits|compare)\/[^/]+\/.+/.test(path))
|
|
47
47
|
throw new ConnectorCallError("invalid_args", `${error.message} Encode '/' inside refs as %2F.`);
|
|
48
48
|
throw error;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// Generated by scripts/generate-providers.mjs; do not edit.
|
|
2
2
|
export const skill = {
|
|
3
|
-
"content": "## Choose the target\n\nAct as the configured GitHub App. `list_scopes` identifies its configured grants\nand reachable repositories. An exact repository grant can narrow its parent\norganization grant. Organization access covers repository operations, including\nfuture repositories in the installation, rather than organization administration.\nA repository rename keeps its ID-bound grant; a replacement at the old name does\nnot inherit it. A transfer cannot use the old installation's token.\n\nUse `search_scoped` for literal-word data search across installations. Each item\ncarries its partition and verified repository. Search completeness can be false\nat GitHub's search limit; narrow the selected scopes or terms when that matters.\nInstallation tokens do not override GitHub's own code-search restrictions.\nKeep the same `limit` with every named-list cursor. Omission uses 30, so a\ncursor issued with limit 30 can continue without an explicit limit.\n\n## Join the right identifiers\n\nAn issue or pull request number belongs to one owner/repository pair; a numeric\nrecord ID is a different identifier. An issue-list response can contain pull\nrequests: the `pull_request` property distinguishes them. Issue IDs returned for\npull requests are not necessarily the PR IDs from the pull-request endpoint.\nReviews join by pull request number; their `commit_id` identifies the reviewed\nrevision. Compare review and head commit IDs when deciding whether feedback is\ncurrent. Issue comments and pull-request review comments are different resources.\n\nRead files at an explicit Git ref when a stable revision matters. Nested contents\npaths are relative: `/repos/alice/widget/contents/lib/index.ts` reads that file,\nand `/repos/alice/widget/contents` lists the root directory. No dot segments,\nempty segments, backslashes or encoded traversal are accepted. In `commits/{ref}`\nand `compare/{basehead}`, encode branch-name slashes as `%2F`, for example\n`commits/feature%2Fx` or `compare/main...feature%2Fx`. Refs stay inside the selected\nrepository; owner-prefixed object refs and cross-repository compares are unavailable.\nFor a PR list, use `head:\"alice:feature/x\"`: the required `login:branch` filter\nselects this repository's PRs and never reads the login's repository. A bare\n`head` branch is refused because GitHub silently ignores it. `base` stays a branch name.\nContents are\nbase64 encoded; decode the inline content before interpreting it. Directory\nlistings have a vendor cap and are not recursive. Missing inline content or a\nmissing patch does not establish an empty file or an unchanged diff. Raw diff,\nlog/archive downloads, secret/admin operations and arbitrary GraphQL are unavailable.\n\n## Release and merge semantics\n\nPublishing a draft release updates that release's `draft` to false. Deleting a\nrelease does not delete its Git tag. Resolve the intended repository and existing\nrelease before changing it; a tag name is not a numeric release ID.\n\nUse `merge_pull_request` for merges. It requires the expected head SHA and a\nsame-repository head and base, plus explicit workflow-write access. GitHub's\natomic SHA guard binds the head rather than the base: a concurrent base retarget\ncan introduce workflow changes after the preflight. A response with `merged:false`\nis a failed merge, not a completed one. Inspect current PR state before a new attempt.\n\n## Conventions\n\n- **Paging:** while `page.hasMore` is true, continue with `page.nextCursor` unchanged. For `github_api_details`, `github_api_read`, `github_api_search`, `list_scopes`, `search_scoped`, 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 30, 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:** `github_api_search` finds this API's operations and reads no account data, `github_api_details` shows one operation's contract, and `github_api_read` calls a reviewed read. `github_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 `github_api_details` with the method and path to
|
|
3
|
+
"content": "## Choose the target\n\nAct as the configured GitHub App. `list_scopes` identifies its configured grants\nand reachable repositories. An exact repository grant can narrow its parent\norganization grant. Organization access covers repository operations, including\nfuture repositories in the installation, rather than organization administration.\nA repository rename keeps its ID-bound grant; a replacement at the old name does\nnot inherit it. A transfer cannot use the old installation's token.\n\nUse `search_scoped` for literal-word data search across installations. Each item\ncarries its partition and verified repository. Search completeness can be false\nat GitHub's search limit; narrow the selected scopes or terms when that matters.\nInstallation tokens do not override GitHub's own code-search restrictions.\nKeep the same `limit` with every named-list cursor. Omission uses 30, so a\ncursor issued with limit 30 can continue without an explicit limit.\n\n## Join the right identifiers\n\nAn issue or pull request number belongs to one owner/repository pair; a numeric\nrecord ID is a different identifier. An issue-list response can contain pull\nrequests: the `pull_request` property distinguishes them. Issue IDs returned for\npull requests are not necessarily the PR IDs from the pull-request endpoint.\nReviews join by pull request number; their `commit_id` identifies the reviewed\nrevision. Compare review and head commit IDs when deciding whether feedback is\ncurrent. Issue comments and pull-request review comments are different resources.\n\nRead files at an explicit Git ref when a stable revision matters. Nested contents\npaths are relative: `/repos/alice/widget/contents/lib/index.ts` reads that file,\nand `/repos/alice/widget/contents` lists the root directory. No dot segments,\nempty segments, backslashes or encoded traversal are accepted. In `commits/{ref}`\nand `compare/{basehead}`, encode branch-name slashes as `%2F`, for example\n`commits/feature%2Fx` or `compare/main...feature%2Fx`. Refs stay inside the selected\nrepository; owner-prefixed object refs and cross-repository compares are unavailable.\nFor a PR list, use `head:\"alice:feature/x\"`: the required `login:branch` filter\nselects this repository's PRs and never reads the login's repository. A bare\n`head` branch is refused because GitHub silently ignores it. `base` stays a branch name.\nContents are\nbase64 encoded; decode the inline content before interpreting it. Directory\nlistings have a vendor cap and are not recursive. Missing inline content or a\nmissing patch does not establish an empty file or an unchanged diff. Raw diff,\nlog/archive downloads, secret/admin operations and arbitrary GraphQL are unavailable.\n\n## Release and merge semantics\n\nPublishing a draft release updates that release's `draft` to false. Deleting a\nrelease does not delete its Git tag. Resolve the intended repository and existing\nrelease before changing it; a tag name is not a numeric release ID.\n\nUse `merge_pull_request` for merges. It requires the expected head SHA and a\nsame-repository head and base, plus explicit workflow-write access. GitHub's\natomic SHA guard binds the head rather than the base: a concurrent base retarget\ncan introduce workflow changes after the preflight. A response with `merged:false`\nis a failed merge, not a completed one. Inspect current PR state before a new attempt.\n\n## Conventions\n\n- **Paging:** while `page.hasMore` is true, continue with `page.nextCursor` unchanged. For `github_api_details`, `github_api_read`, `github_api_search`, `list_scopes`, `search_scoped`, 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 30, 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:** `github_api_search` finds this API's operations and reads no account data, `github_api_details` shows one operation's contract, and `github_api_read` calls a reviewed read. Details' `response.fields` promises selection only; `withheld` hints are incomplete, and absence never means safe or readable. `github_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 `github_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: HTTP 409 is a conflict; a merged:false response and the expected-SHA/same-repository preflight also refuse a merge.\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.",
|
|
4
4
|
"instructionsHeading": "Repository instructions"
|
|
5
5
|
};
|