Mirror of https://github.com/roostorg/coop-integration-example github.com/roostorg/coop-integration-example
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

at main 276 lines 9.0 kB view raw
1/** 2 * Example Coop integration plugin with two signal types: 3 * 1. Random Signal Selection – boolean, probability from org config (tests config saving). 4 * 2. Random Score – numeric 0–100, threshold set in the rule (tests score vs threshold). 5 */ 6 7import { 8 assertModelCardHasRequiredSections, 9 type CoopIntegrationPlugin, 10 type IntegrationManifest, 11 type ModelCard, 12 type PluginSignalContext, 13 type PluginSignalDescriptor, 14} from '@roostorg/types'; 15 16const SIGNAL_TYPE_RANDOM_SELECTION = 'RANDOM_SIGNAL_SELECTION'; 17const SIGNAL_TYPE_RANDOM_SCORE = 'RANDOM_SCORE'; 18const INTEGRATION_ID = 'COOP_INTEGRATION_EXAMPLE'; 19const DEFAULT_TRUE_PERCENTAGE = 50; 20 21const modelCard: ModelCard = { 22 modelName: 'Coop Integration Example', 23 version: '2.0.1', 24 releaseDate: 'March 2026', 25 sections: [ 26 { 27 id: 'trainingData', 28 title: 'Training Data', 29 fields: [ 30 { 31 label: 'Overview', 32 value: 33 'This reference integration does not use a trained model. Outputs are randomly generated for demonstration and testing of COOP rules, configuration, and UI only.', 34 }, 35 ], 36 }, 37 { 38 id: 'policyAndTaxonomy', 39 title: 'Policy and Taxonomy', 40 fields: [ 41 { 42 label: 'Scope', 43 value: 44 'Not a content policy engine. Signals are placeholders: boolean “coin flip” with configurable probability and a numeric random score for threshold exercises in rules.', 45 }, 46 ], 47 }, 48 { 49 id: 'annotationMethodology', 50 title: 'Annotation Methodology', 51 fields: [ 52 { 53 label: 'Method', 54 value: 55 'No human or automated labeling pipeline. Values are produced with Math.random() (or equivalent logic) at evaluation time.', 56 }, 57 ], 58 }, 59 { 60 id: 'performanceBenchmarks', 61 title: 'Performance and Benchmarks', 62 fields: [ 63 { 64 label: 'Benchmarks', 65 value: 66 'No precision, recall, or latency benchmarks apply. Do not use performance claims from this package in production decisions.', 67 }, 68 ], 69 }, 70 { 71 id: 'biasAndLimitations', 72 title: 'Bias and Limitations', 73 fields: [ 74 { 75 label: 'Limitations', 76 value: 77 'Outputs are uncorrelated with input content. Unsuitable for safety, compliance, or moderation decisions. For integration testing and developer learning only.', 78 }, 79 ], 80 }, 81 { 82 id: 'implementationGuidance', 83 title: 'Implementation Guidance', 84 fields: [ 85 { 86 label: 'Signals', 87 value: `${SIGNAL_TYPE_RANDOM_SELECTION} (boolean; org config truePercentage 0–100). ${SIGNAL_TYPE_RANDOM_SCORE} (number 0–100; set threshold and above/below in the rule).`, 88 }, 89 { 90 label: 'Configuration', 91 value: 92 'Random Signal Selection requires org integration config (true percentage). Random Score requires no integration config.', 93 }, 94 { 95 label: 'Versioning', 96 value: 97 'modelCard.version and manifest.version identify this integration plugin release. They are independent of the @roostorg/types dependency major version (e.g. 2.x).', 98 }, 99 ], 100 }, 101 { 102 id: 'relevantLinks', 103 title: 'Relevant Links', 104 fields: [ 105 { 106 label: 'Repository', 107 value: 'https://github.com/roostorg/coop-integration-example', 108 }, 109 { 110 label: 'Documentation', 111 value: 'https://roostorg.github.io/coop/INTEGRATIONS_PLUGIN.html', 112 }, 113 ], 114 }, 115 ], 116}; 117 118assertModelCardHasRequiredSections(modelCard); 119 120const manifest: IntegrationManifest = { 121 id: INTEGRATION_ID, 122 name: 'Coop Integration Example', 123 /** Same semver as modelCard.version: this plugin’s release, not @roostorg/types. */ 124 version: '2.0.1', 125 description: 126 'Example plugin with two signals: config-driven boolean and a numeric score you compare with a threshold in the rule.', 127 docsUrl: 'https://roostorg.github.io/coop/INTEGRATIONS_PLUGIN.html', 128 requiresConfig: true, 129 configurationFields: [ 130 { 131 key: 'truePercentage', 132 label: 'True percentage (0–100)', 133 required: true, 134 inputType: 'text', 135 placeholder: '50', 136 description: 137 'Used by Random Signal Selection only. Probability (0–100) that it returns true. Default 50 if not set.', 138 }, 139 ], 140 signalTypeIds: [SIGNAL_TYPE_RANDOM_SELECTION, SIGNAL_TYPE_RANDOM_SCORE], 141 modelCard, 142 logoPath: 'roost-example-logo.png', 143 logoWithBackgroundPath: 'roost-example-with-background.png', 144}; 145 146/** Parses truePercentage from org config; returns 0–100, default 50 if missing or invalid. */ 147function parseTruePercentage(config: Record<string, unknown>): number { 148 const v = config.truePercentage; 149 if (v === undefined || v === null) return DEFAULT_TRUE_PERCENTAGE; 150 const n = typeof v === 'number' ? v : Number(String(v).trim()); 151 if (!Number.isFinite(n)) return DEFAULT_TRUE_PERCENTAGE; 152 return Math.max(0, Math.min(100, n)); 153} 154 155function hasTruePercentageConfig(config: Record<string, unknown> | null | undefined): boolean { 156 if (config == null) return false; 157 const v = (config as { truePercentage?: unknown }).truePercentage; 158 return v !== undefined && v !== null && String(v).trim() !== ''; 159} 160 161function createRandomSignalSelectionDescriptor( 162 context: PluginSignalContext, 163): PluginSignalDescriptor { 164 const { integrationId, getCredential } = context; 165 const outputType = { scalarType: 'BOOLEAN' as const }; 166 167 return { 168 id: { type: SIGNAL_TYPE_RANDOM_SELECTION }, 169 displayName: 'Coin Flip Selection', 170 description: 171 'Returns true or false at random, with a configurable probability (true percentage 0–100) from the integration config.', 172 docsUrl: null, 173 recommendedThresholds: { 174 highPrecisionThreshold: 0.5, 175 highRecallThreshold: 0.5, 176 }, 177 supportedLanguages: 'ALL', 178 pricingStructure: { type: 'FREE' }, 179 eligibleInputs: ['STRING', 'IMAGE', 'FULL_ITEM'], 180 outputType, 181 getCost: () => 0, 182 needsMatchingValues: false, 183 eligibleSubcategories: [], 184 needsActionPenalties: false, 185 integration: integrationId, 186 allowedInAutomatedRules: true, 187 188 async run(input: unknown): Promise<{ outputType: typeof outputType; score: boolean }> { 189 const orgId = (input as { orgId?: string })?.orgId; 190 if (typeof orgId !== 'string') { 191 return { outputType, score: Math.random() < DEFAULT_TRUE_PERCENTAGE / 100 }; 192 } 193 const config = await getCredential(orgId); 194 const truePct = parseTruePercentage(config ?? {}); 195 const score = Math.random() * 100 < truePct; 196 // Because outputType is { scalarType: 'BOOLEAN' }, Coop will use the output score as is for the condition. 197 return { outputType, score }; 198 }, 199 200 async getDisabledInfo(orgId: string) { 201 const config = await getCredential(orgId); 202 if (hasTruePercentageConfig(config ?? undefined)) { 203 return { disabled: false }; 204 } 205 return { 206 disabled: true, 207 disabledMessage: 208 'Configure the integration (True percentage 0–100) in Org settings to use this signal.', 209 }; 210 }, 211 }; 212} 213 214function createRandomScoreDescriptor( 215 context: PluginSignalContext, 216): PluginSignalDescriptor { 217 const { integrationId } = context; 218 const outputType = { scalarType: 'NUMBER' as const }; 219 220 return { 221 id: { type: SIGNAL_TYPE_RANDOM_SCORE }, 222 displayName: 'Random Score', 223 description: 224 'Returns a random number from 0 up to (but not including) 100. Set a threshold in the rule (e.g. 50) and choose "above" or "below" to test numeric conditions.', 225 docsUrl: null, 226 recommendedThresholds: { 227 highPrecisionThreshold: 50, 228 highRecallThreshold: 50, 229 }, 230 supportedLanguages: 'ALL', 231 pricingStructure: { type: 'FREE' }, 232 eligibleInputs: ['STRING', 'IMAGE', 'FULL_ITEM'], 233 outputType, 234 getCost: () => 0, 235 needsMatchingValues: false, 236 eligibleSubcategories: [], 237 needsActionPenalties: false, 238 integration: integrationId, 239 allowedInAutomatedRules: true, 240 241 async run( 242 _input: unknown, 243 ): Promise<{ outputType: typeof outputType; score: number }> { 244 // [0, 100) — same scale as percentages elsewhere in this plugin (e.g. truePercentage). 245 const score = Math.random() * 100; 246 return { outputType, score }; 247 }, 248 249 async getDisabledInfo() { 250 return { disabled: false }; 251 }, 252 }; 253} 254 255function createSignals( 256 context: PluginSignalContext, 257): ReadonlyArray<{ signalTypeId: string; signal: PluginSignalDescriptor }> { 258 return [ 259 { 260 signalTypeId: SIGNAL_TYPE_RANDOM_SELECTION, 261 signal: createRandomSignalSelectionDescriptor(context), 262 }, 263 { 264 signalTypeId: SIGNAL_TYPE_RANDOM_SCORE, 265 signal: createRandomScoreDescriptor(context), 266 }, 267 ]; 268} 269 270const plugin: CoopIntegrationPlugin = { 271 manifest, 272 createSignals, 273}; 274 275export default plugin; 276export { manifest, createSignals };