import { Data, Effect, Result } from "effect"; import { AuthTemplateSlug, definePlugin, IntegrationDetectionResult, IntegrationSlug, ProviderItemId, ProviderKey, ToolName, connectionIdentifier, tool, type IntegrationRecord, type InvokeToolInput, type PluginCtx, type ResolveToolsInput, type ResolveToolsResult, StorageError, type StorageFailure, type ToolDef, type IntegrationRemovalNotAllowedError,} from "@executor-js/sdk"; import type { IntegrationDecl, ToolDescriptor } from "../pipeline/descriptor";import { PublishError } from "../pipeline/discover";import { ArtifactStoreError } from "../seams/artifact-store";import { ToolSandboxError } from "../seams/tool-sandbox";import { parseGitHubSourceUrl, syncGitHubSource, type GitHubSkippedArtifact, type GitHubSyncResult,} from "../source/github-source";import { slugifyCustomToolsAppName, validateCustomToolsAppSlug } from "../source/app-slug";import type { AppsRuntime, GitHubCustomToolsSourceSummary } from "./runtime";import { makeAppsStore, type AppDescriptorRecord, type GitHubSourceTokenRef } from "./store";import { BindingError, type ClientResolver, type ConnectionCandidate } from "./bindings";import { makePluginCtxAppsResolver } from "./resolver";import { makeAppsRuntimeFromBackings, type AppsBackings } from "./backings"; export const APPS_INTEGRATION_SLUG = "apps";export const APPS_PLUGIN_ID = "apps"; const APP_CONNECTION_NAME = connectionIdentifier("main"); interface AppsGitHubSourceConfig { readonly origin: "github"; readonly kind: "github"; readonly repoUrl: string; readonly repo: string; readonly scope: string; readonly ref?: string; readonly token?: GitHubSourceTokenRef;} type ResolverFactory = (input: { readonly ctx: unknown; readonly scope: string; readonly tool: string;}) => ClientResolver; export class SourceOriginError extends Data.TaggedError("SourceOriginError")<{ readonly message: string; readonly existingOrigin: string; readonly requestedOrigin: string;}> {} export const assertSourceOrigin = ( existingOrigin: string, requestedOrigin: string,): Effect.Effect<void, SourceOriginError> => existingOrigin === requestedOrigin ? Effect.void : Effect.fail( new SourceOriginError({ existingOrigin, requestedOrigin, message: existingOrigin === "github" ? "this app is managed by its GitHub repo" : `this app is managed by its ${existingOrigin} source`, }), ); export type AppsPluginOptions = | { readonly backings: AppsBackings; readonly runtime?: never; readonly makeResolver?: ResolverFactory; } | { readonly runtime: AppsRuntime; readonly backings?: never; readonly makeResolver?: ResolverFactory; }; interface AppsStoreShape { readonly runtime: AppsRuntime;} export interface AppsPluginExtension { readonly runtime: AppsRuntime; readonly syncGitHubSource: (input: unknown) => Effect.Effect<GitHubSyncResult, StorageFailure>; readonly listGitHubSources: () => Effect.Effect<{ readonly sources: readonly GitHubCustomToolsSourceSummary[]; }>; readonly getGitHubSource: (slug: string) => Effect.Effect<{ readonly source: GitHubCustomToolsSourceSummary | null; }>; readonly removeGitHubSource: ( slug: string, ) => Effect.Effect< { readonly removed: true }, StorageFailure | IntegrationRemovalNotAllowedError >;} const isRecord = (value: unknown): value is Record<string, unknown> => value !== null && typeof value === "object" && !Array.isArray(value); const asString = (value: unknown): string | undefined => typeof value === "string" && value.trim().length > 0 ? value.trim() : undefined; const unique = (values: readonly string[]): readonly string[] => [...new Set(values)]; const isPluginCtx = (value: unknown): value is PluginCtx => isRecord(value) && isRecord(value.connections) && typeof value.connections.list === "function" && typeof value.connections.get === "function" && typeof value.execute === "function"; const syncFailure = (message: string, path?: string): GitHubSyncResult => ({ status: "failed", tools: [], skipped: [], errors: [ { stage: "source", message, ...(path ? { diagnostics: [{ path, message }] } : {}), }, ],}); const missingResolver = (): ClientResolver => ({ listConnections: () => Effect.succeed([]), resolveConnection: () => Effect.succeed(null), call: ({ integration, connection }) => Effect.fail( new BindingError({ role: integration, integration, requestedConnection: connection, message: "apps resolver is unavailable outside a request-scoped executor", }), ),}); const missingBackingsMessage = "apps plugin requires backings or runtime"; const missingPublishError = (): PublishError => new PublishError({ message: missingBackingsMessage, stage: "project", diagnostics: [], }); const missingStorageError = (): StorageError => new StorageError({ message: missingBackingsMessage, cause: missingBackingsMessage, }); const missingRuntime = (): AppsRuntime => ({ publish: () => Effect.fail(missingPublishError()), getDescriptor: () => Effect.succeed(null), listGitHubSources: () => Effect.succeed([]), removeSource: () => Effect.fail(missingPublishError()), repair: () => Effect.fail(missingPublishError()), invokeTool: () => Effect.fail(missingPublishError()), deps: { artifactStore: { forScope: () => Effect.fail( new ArtifactStoreError({ message: missingBackingsMessage, }), ), removeScope: () => Effect.fail( new ArtifactStoreError({ message: missingBackingsMessage, }), ), }, sandbox: { collect: () => Effect.fail( new ToolSandboxError({ message: missingBackingsMessage, kind: "collect", }), ), invoke: () => Effect.fail( new ToolSandboxError({ message: missingBackingsMessage, kind: "invoke", }), ), }, store: { putDescriptor: () => Effect.fail(missingStorageError()), getDescriptor: () => Effect.succeed(null), removeDescriptor: () => Effect.fail(missingStorageError()), listDescriptors: () => Effect.succeed([]), }, resolver: missingResolver(), },}); const sourceTokenItemId = (tenant: string, scope: string): ProviderItemId => ProviderItemId.make(`apps:github-source:${tenant}:${scope}:token`); const configBaseUrl = (config: unknown): string | undefined => isRecord(config) && typeof config.baseUrl === "string" && config.baseUrl.length > 0 ? config.baseUrl : undefined; const decodeSourceConfig = (config: unknown): AppsGitHubSourceConfig | null => { if (!isRecord(config) || config.origin !== "github" || config.kind !== "github") return null; const repoUrl = asString(config.repoUrl); const repo = asString(config.repo); const scope = asString(config.scope); if (!repoUrl || !repo || !scope) return null; const ref = asString(config.ref); const token = isRecord(config.token) ? { provider: asString(config.token.provider) ?? "", itemId: asString(config.token.itemId) ?? "", updatedAt: typeof config.token.updatedAt === "number" ? config.token.updatedAt : 0, } : undefined; return { origin: "github", kind: "github", repoUrl, repo, scope, ...(ref ? { ref } : {}), ...(token && token.provider && token.itemId ? { token } : {}), };}; const projectInputSchema = ( schema: unknown, integrations: Readonly<Record<string, IntegrationDecl>>, byRole: Readonly<Record<string, readonly ConnectionCandidate[]>>,): unknown => { const base: Record<string, unknown> = isRecord(schema) ? { ...schema } : { type: "object" }; const properties = isRecord(base.properties) ? { ...base.properties } : {}; const required = new Set( Array.isArray(base.required) ? base.required.filter((value): value is string => typeof value === "string") : [], ); for (const [role, decl] of Object.entries(integrations)) { const addresses = unique((byRole[role] ?? []).map((c) => c.address)); const roleSchema: Record<string, unknown> = { type: "string", enum: addresses, description: `Connection to use for ${role} (${decl.integration})`, }; if (addresses.length === 1) { roleSchema.default = addresses[0]; required.delete(role); } else { required.add(role); } properties[role] = roleSchema; } const projected: Record<string, unknown> = { ...base, type: typeof base.type === "string" ? base.type : "object", properties, }; if (required.size > 0) projected.required = [...required]; else delete projected.required; return projected;}; const projectTool = (descriptor: ToolDescriptor): ToolDef => ({ name: ToolName.make(descriptor.name), description: descriptor.description, inputSchema: descriptor.inputSchema, outputSchema: descriptor.outputSchema, annotations: { requiresApproval: descriptor.annotations?.destructive === true, },}); export const appsPlugin = definePlugin((options?: AppsPluginOptions) => { const runtime = options?.runtime ?? (options?.backings ? makeAppsRuntimeFromBackings(options.backings, missingResolver()) : missingRuntime()); const makeResolver = options?.makeResolver; const tenantFor = (ctx: Pick<PluginCtx, "owner"> | undefined): string => { const tenant = ctx?.owner?.tenant; return tenant === undefined ? "org" : String(tenant); }; const storeSourceToken = ( ctx: PluginCtx<AppsStoreShape>, tenant: string, scope: string, token: string, ): Effect.Effect<GitHubSourceTokenRef | null> => Effect.gen(function* () { const itemId = sourceTokenItemId(tenant, scope); const provider = yield* ctx.providers.setDefault(itemId, token).pipe(Effect.result); if (Result.isFailure(provider)) return null; return { provider: String(provider.success), itemId: String(itemId), updatedAt: Date.now(), }; }); const storedSourceToken = ( ctx: PluginCtx<AppsStoreShape>, ref: GitHubSourceTokenRef | undefined, ): Effect.Effect<string | null> => { if (!ref) return Effect.succeed(null); return ctx.providers .get(ProviderKey.make(ref.provider), ProviderItemId.make(ref.itemId)) .pipe(Effect.orElseSucceed(() => null)); }; const removeStoredSourceToken = ( ctx: PluginCtx<AppsStoreShape>, ref: GitHubSourceTokenRef | undefined, ): Effect.Effect<void, unknown> => ref ? ctx.providers.remove(ProviderKey.make(ref.provider), ProviderItemId.make(ref.itemId)) : Effect.void; const ensureAppConnection = (slug: IntegrationSlug, ctx: PluginCtx<AppsStoreShape>) => Effect.gen(function* () { const conns = yield* ctx.connections .list({ integration: slug }) .pipe(Effect.orElseSucceed(() => [])); if (!conns.some((connection) => String(connection.name) === String(APP_CONNECTION_NAME))) { yield* ctx.connections.create({ owner: "user", name: APP_CONNECTION_NAME, integration: slug, template: AuthTemplateSlug.make("none"), value: "", }); } return { owner: "user" as const, integration: slug, name: APP_CONNECTION_NAME }; }); const refreshAppConnection = (slug: IntegrationSlug, ctx: PluginCtx<AppsStoreShape>) => Effect.gen(function* () { const ref = yield* ensureAppConnection(slug, ctx); yield* ctx.connections.refresh(ref).pipe(Effect.orElseSucceed(() => [])); return ref; }); const descriptorRecordByScope = (tenant: string) => runtime.deps.store.listDescriptors(tenant).pipe( Effect.orElseSucceed(() => []), Effect.map((records) => new Map(records.map((record) => [record.descriptor.scope, record]))), ); const sourceSummaryFor = ( integration: IntegrationRecord, recordsByScope: ReadonlyMap<string, AppDescriptorRecord>, ): GitHubCustomToolsSourceSummary | null => { const config = decodeSourceConfig(integration.config); if (!config) return null; const record = recordsByScope.get(config.scope); if (!record) return null; const descriptor = record.descriptor; const source = descriptor?.source; if (source?.kind !== "github") return null; return { slug: String(integration.slug), name: integration.name, scope: descriptor.scope, url: source.url, repo: source.repo, ref: source.ref, hasToken: config.token !== undefined, upstreamSha: source.upstreamSha, snapshotId: descriptor.snapshotId, ...(descriptor.description ? { description: descriptor.description } : {}), publishedAt: new Date(record.publishedAt).toISOString(), tools: descriptor.tools.map((toolDesc) => toolDesc.name), skipped: [ ...(source.skipped ?? []), ...(descriptor.skipped as readonly GitHubSkippedArtifact[]), ], }; }; const listSources = (ctx: PluginCtx<AppsStoreShape>) => Effect.gen(function* () { const tenant = tenantFor(ctx); const recordsByScope = yield* descriptorRecordByScope(tenant); const integrations = yield* ctx.core.integrations.list().pipe(Effect.orElseSucceed(() => [])); const sources: GitHubCustomToolsSourceSummary[] = []; for (const integration of integrations) { if (integration.kind !== APPS_PLUGIN_ID) continue; const record = yield* ctx.core.integrations .get(integration.slug) .pipe(Effect.orElseSucceed(() => null)); if (!record) continue; const summary = sourceSummaryFor(record, recordsByScope); if (summary) sources.push(summary); } return sources.sort((a, b) => b.publishedAt.localeCompare(a.publishedAt)); }); const getSource = (ctx: PluginCtx<AppsStoreShape>, slug: string) => Effect.gen(function* () { const tenant = tenantFor(ctx); const recordsByScope = yield* descriptorRecordByScope(tenant); const record = yield* ctx.core.integrations .get(IntegrationSlug.make(slug)) .pipe(Effect.orElseSucceed(() => null)); return record && record.kind === APPS_PLUGIN_ID ? sourceSummaryFor(record, recordsByScope) : null; }); const syncExistingSource = ( ctx: PluginCtx<AppsStoreShape>, slugValue: string, providedToken: string | null, ) => Effect.gen(function* () { const slug = IntegrationSlug.make(slugValue); const record = yield* ctx.core.integrations.get(slug).pipe(Effect.orElseSucceed(() => null)); if (!record) { return syncFailure(`Custom tools source "${slugValue}" does not exist.`); } if (record.kind === APPS_PLUGIN_ID && isRecord(record.config)) { const origin = asString(record.config.origin); if (origin) { const originCheck = yield* assertSourceOrigin(origin, "github").pipe(Effect.result); if (Result.isFailure(originCheck)) return syncFailure(originCheck.failure.message); } } const config = record.kind === APPS_PLUGIN_ID ? decodeSourceConfig(record.config) : null; if (!config) return syncFailure(`Custom tools source "${slugValue}" does not exist.`); const github = yield* ctx.core.integrations .get(IntegrationSlug.make("github")) .pipe(Effect.orElseSucceed(() => null)); const tenant = tenantFor(ctx); const token = providedToken ?? (yield* storedSourceToken(ctx, config.token)); const result = yield* syncGitHubSource({ runtime, tenant, scope: config.scope, url: config.repoUrl, ...(config.ref ? { ref: config.ref } : {}), token, baseUrl: configBaseUrl(github?.config), }); if (result.status === "failed") return result; const tokenRef = providedToken ? yield* storeSourceToken(ctx, tenant, config.scope, providedToken) : config.token; if (providedToken && !tokenRef) { return syncFailure( "No writable credential provider is available to store the GitHub source token.", ); } const descriptor = yield* runtime.getDescriptor(tenant, config.scope); const source = descriptor?.source?.kind === "github" ? descriptor.source : null; const nextConfig: AppsGitHubSourceConfig = { ...config, ...(source ? { repoUrl: source.url, repo: source.repo, ref: source.ref, } : {}), ...(tokenRef ? { token: tokenRef } : {}), };Lines 1–500 of 801. Load more
import { Data, Effect, Result } from "effect"; import { AuthTemplateSlug, definePlugin, IntegrationDetectionResult, IntegrationSlug, ProviderItemId, ProviderKey, ToolName, connectionIdentifier, tool, type IntegrationRecord, type InvokeToolInput, type PluginCtx, type ResolveToolsInput, type ResolveToolsResult, StorageError, type StorageFailure, type ToolDef, type IntegrationRemovalNotAllowedError, } from "@executor-js/sdk"; import type { IntegrationDecl, ToolDescriptor } from "../pipeline/descriptor"; import { PublishError } from "../pipeline/discover"; import { ArtifactStoreError } from "../seams/artifact-store"; import { ToolSandboxError } from "../seams/tool-sandbox"; import { parseGitHubSourceUrl, syncGitHubSource, type GitHubSkippedArtifact, type GitHubSyncResult, } from "../source/github-source"; import { slugifyCustomToolsAppName, validateCustomToolsAppSlug } from "../source/app-slug"; import type { AppsRuntime, GitHubCustomToolsSourceSummary } from "./runtime"; import { makeAppsStore, type AppDescriptorRecord, type GitHubSourceTokenRef } from "./store"; import { BindingError, type ClientResolver, type ConnectionCandidate } from "./bindings"; import { makePluginCtxAppsResolver } from "./resolver"; import { makeAppsRuntimeFromBackings, type AppsBackings } from "./backings"; export const APPS_INTEGRATION_SLUG = "apps"; export const APPS_PLUGIN_ID = "apps"; const APP_CONNECTION_NAME = connectionIdentifier("main"); interface AppsGitHubSourceConfig { readonly origin: "github"; readonly kind: "github"; readonly repoUrl: string; readonly repo: string; readonly scope: string; readonly ref?: string; readonly token?: GitHubSourceTokenRef; } type ResolverFactory = (input: { readonly ctx: unknown; readonly scope: string; readonly tool: string; }) => ClientResolver; export class SourceOriginError extends Data.TaggedError("SourceOriginError")<{ readonly message: string; readonly existingOrigin: string; readonly requestedOrigin: string; }> {} export const assertSourceOrigin = ( existingOrigin: string, requestedOrigin: string, ): Effect.Effect<void, SourceOriginError> => existingOrigin === requestedOrigin ? Effect.void : Effect.fail( new SourceOriginError({ existingOrigin, requestedOrigin, message: existingOrigin === "github" ? "this app is managed by its GitHub repo" : `this app is managed by its ${existingOrigin} source`, }), ); export type AppsPluginOptions = | { readonly backings: AppsBackings; readonly runtime?: never; readonly makeResolver?: ResolverFactory; } | { readonly runtime: AppsRuntime; readonly backings?: never; readonly makeResolver?: ResolverFactory; }; interface AppsStoreShape { readonly runtime: AppsRuntime; } export interface AppsPluginExtension { readonly runtime: AppsRuntime; readonly syncGitHubSource: (input: unknown) => Effect.Effect<GitHubSyncResult, StorageFailure>; readonly listGitHubSources: () => Effect.Effect<{ readonly sources: readonly GitHubCustomToolsSourceSummary[]; }>; readonly getGitHubSource: (slug: string) => Effect.Effect<{ readonly source: GitHubCustomToolsSourceSummary | null; }>; readonly removeGitHubSource: ( slug: string, ) => Effect.Effect< { readonly removed: true }, StorageFailure | IntegrationRemovalNotAllowedError >; } const isRecord = (value: unknown): value is Record<string, unknown> => value !== null && typeof value === "object" && !Array.isArray(value); const asString = (value: unknown): string | undefined => typeof value === "string" && value.trim().length > 0 ? value.trim() : undefined; const unique = (values: readonly string[]): readonly string[] => [...new Set(values)]; const isPluginCtx = (value: unknown): value is PluginCtx => isRecord(value) && isRecord(value.connections) && typeof value.connections.list === "function" && typeof value.connections.get === "function" && typeof value.execute === "function"; const syncFailure = (message: string, path?: string): GitHubSyncResult => ({ status: "failed", tools: [], skipped: [], errors: [ { stage: "source", message, ...(path ? { diagnostics: [{ path, message }] } : {}), }, ], }); const missingResolver = (): ClientResolver => ({ listConnections: () => Effect.succeed([]), resolveConnection: () => Effect.succeed(null), call: ({ integration, connection }) => Effect.fail( new BindingError({ role: integration, integration, requestedConnection: connection, message: "apps resolver is unavailable outside a request-scoped executor", }), ), }); const missingBackingsMessage = "apps plugin requires backings or runtime"; const missingPublishError = (): PublishError => new PublishError({ message: missingBackingsMessage, stage: "project", diagnostics: [], }); const missingStorageError = (): StorageError => new StorageError({ message: missingBackingsMessage, cause: missingBackingsMessage, }); const missingRuntime = (): AppsRuntime => ({ publish: () => Effect.fail(missingPublishError()), getDescriptor: () => Effect.succeed(null), listGitHubSources: () => Effect.succeed([]), removeSource: () => Effect.fail(missingPublishError()), repair: () => Effect.fail(missingPublishError()), invokeTool: () => Effect.fail(missingPublishError()), deps: { artifactStore: { forScope: () => Effect.fail( new ArtifactStoreError({ message: missingBackingsMessage, }), ), removeScope: () => Effect.fail( new ArtifactStoreError({ message: missingBackingsMessage, }), ), }, sandbox: { collect: () => Effect.fail( new ToolSandboxError({ message: missingBackingsMessage, kind: "collect", }), ), invoke: () => Effect.fail( new ToolSandboxError({ message: missingBackingsMessage, kind: "invoke", }), ), }, store: { putDescriptor: () => Effect.fail(missingStorageError()), getDescriptor: () => Effect.succeed(null), removeDescriptor: () => Effect.fail(missingStorageError()), listDescriptors: () => Effect.succeed([]), }, resolver: missingResolver(), }, }); const sourceTokenItemId = (tenant: string, scope: string): ProviderItemId => ProviderItemId.make(`apps:github-source:${tenant}:${scope}:token`); const configBaseUrl = (config: unknown): string | undefined => isRecord(config) && typeof config.baseUrl === "string" && config.baseUrl.length > 0 ? config.baseUrl : undefined; const decodeSourceConfig = (config: unknown): AppsGitHubSourceConfig | null => { if (!isRecord(config) || config.origin !== "github" || config.kind !== "github") return null; const repoUrl = asString(config.repoUrl); const repo = asString(config.repo); const scope = asString(config.scope); if (!repoUrl || !repo || !scope) return null; const ref = asString(config.ref); const token = isRecord(config.token) ? { provider: asString(config.token.provider) ?? "", itemId: asString(config.token.itemId) ?? "", updatedAt: typeof config.token.updatedAt === "number" ? config.token.updatedAt : 0, } : undefined; return { origin: "github", kind: "github", repoUrl, repo, scope, ...(ref ? { ref } : {}), ...(token && token.provider && token.itemId ? { token } : {}), }; }; const projectInputSchema = ( schema: unknown, integrations: Readonly<Record<string, IntegrationDecl>>, byRole: Readonly<Record<string, readonly ConnectionCandidate[]>>, ): unknown => { const base: Record<string, unknown> = isRecord(schema) ? { ...schema } : { type: "object" }; const properties = isRecord(base.properties) ? { ...base.properties } : {}; const required = new Set( Array.isArray(base.required) ? base.required.filter((value): value is string => typeof value === "string") : [], ); for (const [role, decl] of Object.entries(integrations)) { const addresses = unique((byRole[role] ?? []).map((c) => c.address)); const roleSchema: Record<string, unknown> = { type: "string", enum: addresses, description: `Connection to use for ${role} (${decl.integration})`, }; if (addresses.length === 1) { roleSchema.default = addresses[0]; required.delete(role); } else { required.add(role); } properties[role] = roleSchema; } const projected: Record<string, unknown> = { ...base, type: typeof base.type === "string" ? base.type : "object", properties, }; if (required.size > 0) projected.required = [...required]; else delete projected.required; return projected; }; const projectTool = (descriptor: ToolDescriptor): ToolDef => ({ name: ToolName.make(descriptor.name), description: descriptor.description, inputSchema: descriptor.inputSchema, outputSchema: descriptor.outputSchema, annotations: { requiresApproval: descriptor.annotations?.destructive === true, }, }); export const appsPlugin = definePlugin((options?: AppsPluginOptions) => { const runtime = options?.runtime ?? (options?.backings ? makeAppsRuntimeFromBackings(options.backings, missingResolver()) : missingRuntime()); const makeResolver = options?.makeResolver; const tenantFor = (ctx: Pick<PluginCtx, "owner"> | undefined): string => { const tenant = ctx?.owner?.tenant; return tenant === undefined ? "org" : String(tenant); }; const storeSourceToken = ( ctx: PluginCtx<AppsStoreShape>, tenant: string, scope: string, token: string, ): Effect.Effect<GitHubSourceTokenRef | null> => Effect.gen(function* () { const itemId = sourceTokenItemId(tenant, scope); const provider = yield* ctx.providers.setDefault(itemId, token).pipe(Effect.result); if (Result.isFailure(provider)) return null; return { provider: String(provider.success), itemId: String(itemId), updatedAt: Date.now(), }; }); const storedSourceToken = ( ctx: PluginCtx<AppsStoreShape>, ref: GitHubSourceTokenRef | undefined, ): Effect.Effect<string | null> => { if (!ref) return Effect.succeed(null); return ctx.providers .get(ProviderKey.make(ref.provider), ProviderItemId.make(ref.itemId)) .pipe(Effect.orElseSucceed(() => null)); }; const removeStoredSourceToken = ( ctx: PluginCtx<AppsStoreShape>, ref: GitHubSourceTokenRef | undefined, ): Effect.Effect<void, unknown> => ref ? ctx.providers.remove(ProviderKey.make(ref.provider), ProviderItemId.make(ref.itemId)) : Effect.void; const ensureAppConnection = (slug: IntegrationSlug, ctx: PluginCtx<AppsStoreShape>) => Effect.gen(function* () { const conns = yield* ctx.connections .list({ integration: slug }) .pipe(Effect.orElseSucceed(() => [])); if (!conns.some((connection) => String(connection.name) === String(APP_CONNECTION_NAME))) { yield* ctx.connections.create({ owner: "user", name: APP_CONNECTION_NAME, integration: slug, template: AuthTemplateSlug.make("none"), value: "", }); } return { owner: "user" as const, integration: slug, name: APP_CONNECTION_NAME }; }); const refreshAppConnection = (slug: IntegrationSlug, ctx: PluginCtx<AppsStoreShape>) => Effect.gen(function* () { const ref = yield* ensureAppConnection(slug, ctx); yield* ctx.connections.refresh(ref).pipe(Effect.orElseSucceed(() => [])); return ref; }); const descriptorRecordByScope = (tenant: string) => runtime.deps.store.listDescriptors(tenant).pipe( Effect.orElseSucceed(() => []), Effect.map((records) => new Map(records.map((record) => [record.descriptor.scope, record]))), ); const sourceSummaryFor = ( integration: IntegrationRecord, recordsByScope: ReadonlyMap<string, AppDescriptorRecord>, ): GitHubCustomToolsSourceSummary | null => { const config = decodeSourceConfig(integration.config); if (!config) return null; const record = recordsByScope.get(config.scope); if (!record) return null; const descriptor = record.descriptor; const source = descriptor?.source; if (source?.kind !== "github") return null; return { slug: String(integration.slug), name: integration.name, scope: descriptor.scope, url: source.url, repo: source.repo, ref: source.ref, hasToken: config.token !== undefined, upstreamSha: source.upstreamSha, snapshotId: descriptor.snapshotId, ...(descriptor.description ? { description: descriptor.description } : {}), publishedAt: new Date(record.publishedAt).toISOString(), tools: descriptor.tools.map((toolDesc) => toolDesc.name), skipped: [ ...(source.skipped ?? []), ...(descriptor.skipped as readonly GitHubSkippedArtifact[]), ], }; }; const listSources = (ctx: PluginCtx<AppsStoreShape>) => Effect.gen(function* () { const tenant = tenantFor(ctx); const recordsByScope = yield* descriptorRecordByScope(tenant); const integrations = yield* ctx.core.integrations.list().pipe(Effect.orElseSucceed(() => [])); const sources: GitHubCustomToolsSourceSummary[] = []; for (const integration of integrations) { if (integration.kind !== APPS_PLUGIN_ID) continue; const record = yield* ctx.core.integrations .get(integration.slug) .pipe(Effect.orElseSucceed(() => null)); if (!record) continue; const summary = sourceSummaryFor(record, recordsByScope); if (summary) sources.push(summary); } return sources.sort((a, b) => b.publishedAt.localeCompare(a.publishedAt)); }); const getSource = (ctx: PluginCtx<AppsStoreShape>, slug: string) => Effect.gen(function* () { const tenant = tenantFor(ctx); const recordsByScope = yield* descriptorRecordByScope(tenant); const record = yield* ctx.core.integrations .get(IntegrationSlug.make(slug)) .pipe(Effect.orElseSucceed(() => null)); return record && record.kind === APPS_PLUGIN_ID ? sourceSummaryFor(record, recordsByScope) : null; }); const syncExistingSource = ( ctx: PluginCtx<AppsStoreShape>, slugValue: string, providedToken: string | null, ) => Effect.gen(function* () { const slug = IntegrationSlug.make(slugValue); const record = yield* ctx.core.integrations.get(slug).pipe(Effect.orElseSucceed(() => null)); if (!record) { return syncFailure(`Custom tools source "${slugValue}" does not exist.`); } if (record.kind === APPS_PLUGIN_ID && isRecord(record.config)) { const origin = asString(record.config.origin); if (origin) { const originCheck = yield* assertSourceOrigin(origin, "github").pipe(Effect.result); if (Result.isFailure(originCheck)) return syncFailure(originCheck.failure.message); } } const config = record.kind === APPS_PLUGIN_ID ? decodeSourceConfig(record.config) : null; if (!config) return syncFailure(`Custom tools source "${slugValue}" does not exist.`); const github = yield* ctx.core.integrations .get(IntegrationSlug.make("github")) .pipe(Effect.orElseSucceed(() => null)); const tenant = tenantFor(ctx); const token = providedToken ?? (yield* storedSourceToken(ctx, config.token)); const result = yield* syncGitHubSource({ runtime, tenant, scope: config.scope, url: config.repoUrl, ...(config.ref ? { ref: config.ref } : {}), token, baseUrl: configBaseUrl(github?.config), }); if (result.status === "failed") return result; const tokenRef = providedToken ? yield* storeSourceToken(ctx, tenant, config.scope, providedToken) : config.token; if (providedToken && !tokenRef) { return syncFailure( "No writable credential provider is available to store the GitHub source token.", ); } const descriptor = yield* runtime.getDescriptor(tenant, config.scope); const source = descriptor?.source?.kind === "github" ? descriptor.source : null; const nextConfig: AppsGitHubSourceConfig = { ...config, ...(source ? { repoUrl: source.url, repo: source.repo, ref: source.ref, } : {}), ...(tokenRef ? { token: tokenRef } : {}), };