Ctrl K
ComunidadGuías…

scripts/verify-composio-managed-gmail.mjs

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
import assert from 'node:assert/strict';

import { SqliteSettingsStore } from '../src/config/settings-store.ts';
import { SqliteConfigStore } from '../src/config/store.ts';
import { agentRoutineDeletion } from '../src/routines/store.ts';
import { MANAGED_CONNECTOR_CATALOG } from '../src/connections/catalog/index.ts';
import {
  createManagedConnectionProviderRegistry,
  invokeManagedConnectionCapability,
} from '../src/connections/managed.ts';
import { ComposioManagedConnectionProvider } from '../src/connections/providers/composio.ts';
import { ConnectionAccountService } from '../src/connections/store.ts';
import { SqliteUsageStore } from '../src/usage/store.ts';
import { MANAGED_CONNECTOR_CANARY_MANIFESTS } from '../src/connections/canary-manifests.ts';

const apiKey = requiredEnvironmentValue('COMPOSIO_API_KEY');
const accountRef = requiredEnvironmentValue('COMPOSIO_CONNECTED_ACCOUNT_ID');
const principalRef = requiredEnvironmentValue('COMPOSIO_USER_ID');
const connectorInput = process.env.COMPOSIO_CONNECTOR?.trim().toLowerCase() || 'gmail';
const accessLane = parseAccessLane(process.env.COMPOSIO_ACCESS_LANE);
const connector = MANAGED_CONNECTOR_CATALOG.list().find(
  ({ id, toolkit }) => id === connectorInput || toolkit === connectorInput,
);
if (!connector) throw new Error(`Unknown COMPOSIO_CONNECTOR ${JSON.stringify(connectorInput)}`);
const manifest = MANAGED_CONNECTOR_CANARY_MANIFESTS[connector.toolkit];
if (!manifest) throw new Error(`No safe canary manifest exists for ${connector.toolkit}`);

const laneCapabilities = connector.capabilities.filter(({ accessLane: capabilityLane }) =>
  accessLane === 'read' ? capabilityLane === 'read' : capabilityLane === 'write');
const configuredCapability = process.env.COMPOSIO_CAPABILITY?.trim();
if (accessLane === 'write' && !configuredCapability) {
  throw new Error('COMPOSIO_CAPABILITY is required for a write canary');
}
const capabilityId = configuredCapability || manifest.capability || laneCapabilities[0]?.id;
const capability = capabilityId && MANAGED_CONNECTOR_CATALOG.capability(capabilityId);
if (!capability || capability.connectorToolkit !== connector.toolkit ||
    (accessLane === 'read' && capability.accessLane !== 'read')) {
  throw new Error('COMPOSIO_CAPABILITY is not available for the selected connector/access lane');
}
const arguments_ = parseJsonObject('COMPOSIO_ARGUMENTS_JSON', manifest.arguments);
const resourceConstraints = parseJsonObject('COMPOSIO_RESOURCE_CONSTRAINTS_JSON', {});
if (!configuredCapability || capability.id === manifest.capability) {
  requireManifestInputs(manifest, arguments_, resourceConstraints);
}
const dataModifying = capability.effect !== 'read';
if (dataModifying && process.env.COMPOSIO_ALLOW_WRITE_CANARY !== '1') {
  throw new Error(
    `Capability ${capability.id} can modify external state. `
      + 'Set COMPOSIO_ALLOW_WRITE_CANARY=1 only after reviewing COMPOSIO_ARGUMENTS_JSON.',
  );
}

const workspaceId = 'T_COMPOSIO_VERIFY';
const agentId = 'agent_composio_verify';
const membershipId = 'membership_composio_verify';
const config = new SqliteConfigStore(':memory:', agentRoutineDeletion, { agents: [] });
const settings = new SqliteSettingsStore(':memory:');
const usage = new SqliteUsageStore(':memory:');
const provider = new ComposioManagedConnectionProvider({ apiKey });
const providers = createManagedConnectionProviderRegistry([provider]);
const service = new ConnectionAccountService({ config, settings, managedProviders: providers });

const principal = {
  userId: 'user_composio_verify',
  membershipId,
  organizationId: 'organization_composio_verify',
  role: 'owner',
  authenticatorKind: 'test',
  credentialId: 'credential_composio_verify',
  correlationId: crypto.randomUUID(),
  machine: false,
};

const identity = activeIdentity({ workspaceId, membershipId, principal });

try {
  await config.createAgent({
    id: agentId,
    name: 'Composio verifier',
    instructions: `Verify the managed ${connector.label} connection.`,
    enabled: true,
    creatorMembershipId: membershipId,
    editPolicy: 'creator_and_admins',
    skills: [],
    mcpServers: [],
    apiConnections: [],
    repositories: [],
  });
  await config.ensureWorkspaceInstallation({
    workspaceId,
    transportMode: 'direct',
    defaultAgentId: agentId,
  });
  const account = await service.create({
    principal,
    workspaceId,
    ownerKind: 'member',
    providerId: connector.providerId,
    label: `Live Composio ${connector.label}`,
    policy: {
      kind: 'managed',
      adapterId: 'composio',
      toolkit: connector.toolkit,
      principalRef,
      accountRef,
      allowedCapabilities: [capability.id],
      resourceConstraints,
    },
  });
  await service.attach({
    principal,
    agentId,
    connectionAccountId: account.id,
    allowedCapabilities: [capability.id],
    resourceConstraints: Object.fromEntries(
      Object.entries(resourceConstraints).map(([key, selections]) => [
        key,
        Array.isArray(selections) ? selections.map(({ handle }) => handle) : [],
      ]),
    ),
  });

  if (dataModifying) {
    console.error(JSON.stringify({
      event: 'chickpea.composio_live_verifier.data_modifying_canary',
      connector: connector.id,
      capability: capability.id,
      effectClass: capability.effect,
    }));
  }

  const operationId = `composio-canary:${crypto.randomUUID()}`;
  const result = await invokeManagedConnectionCapability({
    config,
    identity,
    providers,
    workspaceId,
    agentId,
    actorMembershipId: membershipId,
    connectionAccountId: account.id,
    capability: capability.id,
    arguments: arguments_,
    usage,
    correlation: { operationId },
  });
  const summary = await usage.summarizeConnectorUsage({
    from: 0,
    to: Date.now() + 1,
    workspaceId,
    toolkit: connector.toolkit,
  });
  assert.equal(account.lifecycle, 'ready');
  assert.equal(summary.attemptCount, 1);
  assert.equal(summary.groups[0]?.outcome, 'success');

  console.log(JSON.stringify({
    ok: true,
    connector: connector.id,
    toolkit: connector.toolkit,
    accessLane,
    capability: capability.id,
    effectClass: capability.effect,
    dataModifyingCanary: dataModifying,
    accountLifecycle: account.lifecycle,
    providerVersion: result.providerVersion ?? null,
    providerLogId: result.logId ?? null,
    resultKeys: Object.keys(result.data).sort(),
    resultBytes: new TextEncoder().encode(result.serializedData).byteLength,
    measurement: {
      outcome: summary.groups[0]?.outcome ?? null,
      providerToolCallCount: summary.providerToolCallCount,
      estimatedCostMicros: summary.estimatedCostMicros,
    },
  }, null, 2));
} finally {
  config.close();
  settings.close();
  usage.close();
}

function requiredEnvironmentValue(name) {
  const value = process.env[name]?.trim();
  if (!value) throw new Error(`${name} is required`);
  return value;
}

function parseAccessLane(value) {
  const lane = value?.trim().toLowerCase() || 'read';
  if (lane !== 'read' && lane !== 'write') {
    throw new Error('COMPOSIO_ACCESS_LANE must be read or write');
  }
  return lane;
}

function parseJsonObject(name, fallback) {
  const input = process.env[name]?.trim();
  if (!input) return fallback;
  let parsed;
  try {
    parsed = JSON.parse(input);
  } catch {
    throw new Error(`${name} must contain valid JSON`);
  }
  if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
    throw new Error(`${name} must contain a JSON object`);
  }
  return parsed;
}

function requireManifestInputs(manifest, arguments_, resourceConstraints) {
  const missingArguments = manifest.requiredArguments.filter((name) => {
    const value = arguments_[name];
    return typeof value !== 'string' || !value.trim();
  });
  const missingResources = manifest.requiredResourceConstraints.filter((name) => {
    const value = resourceConstraints[name];
    return !Array.isArray(value) || value.length === 0;
  });
  if (missingArguments.length || missingResources.length) {
    throw new Error([
      missingArguments.length
        ? `COMPOSIO_ARGUMENTS_JSON must include: ${missingArguments.join(', ')}`
        : '',
      missingResources.length
        ? `COMPOSIO_RESOURCE_CONSTRAINTS_JSON must include: ${missingResources.join(', ')}`
        : '',
    ].filter(Boolean).join('; '));
  }
}

function activeIdentity({ workspaceId: teamId, membershipId: memberId, principal: actor }) {
  const user = {
    id: actor.userId,
    slackTeamId: teamId,
    slackUserId: 'U_COMPOSIO_VERIFY',
    displayName: 'Composio verifier',
    contactEmail: null,
    createdAt: 1,
    updatedAt: 1,
  };
  const membership = {
    id: memberId,
    organizationId: actor.organizationId,
    userId: user.id,
    role: 'owner',
    status: 'active',
    createdAt: 1,
    updatedAt: 1,
  };
  const binding = {
    id: 'binding_composio_verify',
    provider: 'slack',
    slackTeamId: teamId,
    slackUserId: user.slackUserId,
    userId: user.id,
    organizationId: membership.organizationId,
    membershipId: membership.id,
    betterAuthUserId: null,
    betterAuthMembershipId: null,
    revision: 1,
    createdAt: 1,
    updatedAt: 1,
  };
  return {
    getOrganization: async () => ({
      id: actor.organizationId,
      displayName: 'Composio verification',
      slackTeamId: teamId,
      authMode: 'slack_active',
      canonicalAdminOrigin: 'https://chickpea.invalid',
      createdAt: 1,
      updatedAt: 1,
    }),
    getMembership: async () => membership,
    getMembershipAccessOverlay: async () => undefined,
    getUser: async () => user,
    resolveSlackIdentity: async () => ({ user, membership, binding }),
  };
}