Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/lean-connection-health.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@executor-js/sdk": patch
---

Keep `connections.list` health output compact unless callers opt into diagnostics with `verbose: true`. Default list responses now retain only the health status, identity, and check timestamp; verbose responses continue to include HTTP status, diagnostic detail, and bounded upstream response samples.
60 changes: 59 additions & 1 deletion packages/core/sdk/src/connections.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { describe, expect, it } from "@effect/vitest";
import { Effect, Predicate, Result } from "effect";
import { Effect, Predicate, Result, Schema } from "effect";

import {
AuthTemplateSlug,
Expand All @@ -11,6 +11,7 @@ import {
ToolName,
} from "./ids";
import { createExecutor } from "./executor";
import { HealthCheckResult } from "./health-check";
import { definePlugin } from "./plugin";
import type { CredentialProvider } from "./provider";
import { makeTestConfig, makeTestExecutor } from "./testing";
Expand Down Expand Up @@ -43,6 +44,11 @@ const memoryProvider = (): CredentialProvider => {
const INTEG = IntegrationSlug.make("vercel");
const TEMPLATE = AuthTemplateSlug.make("apiKey");

const ConnectionListHealthOutput = Schema.Struct({
connections: Schema.Array(Schema.Struct({ lastHealth: Schema.NullOr(HealthCheckResult) })),
});
const decodeConnectionListHealthOutput = Schema.decodeUnknownEffect(ConnectionListHealthOutput);

const demoPlugin = definePlugin(() => ({
id: "demo" as const,
credentialProviders: [memoryProvider()],
Expand Down Expand Up @@ -263,6 +269,58 @@ describe("connections.create", () => {
});

describe("connections.list / get", () => {
it.effect("only includes full health diagnostics in verbose core tool output", () =>
Effect.gen(function* () {
const config = makeTestConfig({ plugins: [demoPlugin] as const, coreTools: {} });
const executor = yield* createExecutor(config);
yield* executor.demo.seed();
yield* executor.connections.create({
owner: "org",
name: ConnectionName.make("health"),
integration: INTEG,
template: TEMPLATE,
value: "v",
});

const health = {
status: "healthy" as const,
identity: "account@example.com",
checkedAt: 1234,
httpStatus: 200,
detail: "GET /me returned 200",
responseSample: [{ path: "user.email", value: "account@example.com" }],
};
yield* Effect.promise(() =>
config.db.updateMany("connection", {
where: (b) => b.and(b("integration", "=", String(INTEG)), b("name", "=", "health")),
set: { last_health: health },
}),
);

const list = (input: { readonly verbose?: boolean }) =>
executor
.execute(ToolAddress.make("executor.coreTools.connections.list"), {
integration: String(INTEG),
owner: "org",
...input,
})
.pipe(Effect.flatMap(decodeConnectionListHealthOutput));

const defaultList = yield* list({});
const nonVerboseList = yield* list({ verbose: false });
const verboseList = yield* list({ verbose: true });
const summary = {
status: "healthy",
identity: "account@example.com",
checkedAt: 1234,
};

expect(defaultList.connections[0]?.lastHealth).toEqual(summary);
expect(nonVerboseList.connections[0]?.lastHealth).toEqual(summary);
expect(verboseList.connections[0]?.lastHealth).toEqual(health);
}),
);

it.effect("lists created connections and filters by integration", () =>
Effect.gen(function* () {
const executor = yield* setup();
Expand Down
24 changes: 17 additions & 7 deletions packages/core/sdk/src/core-tools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,10 +88,10 @@ const ConnectionsListInput = Schema.Struct({
verbose: Schema.optional(Schema.Boolean),
});

/** Lean per-connection shape for list scans. Omits the full `oauthScope`
* grant string (a single connection's scope list can run to thousands of
* characters and dominates the payload) in favor of `oauthScopeCount`. The
* full scope is included only when the caller passes `verbose: true`. */
/** Lean per-connection shape for list scans. The default projection summarizes
* the full `oauthScope` grant string as `oauthScopeCount` and trims health
* probe diagnostics. Those optional fields are populated only for `verbose:
* true`. */
const ConnectionListItem = Schema.Struct({
owner: OwnerSchema,
name: Schema.String,
Expand Down Expand Up @@ -389,8 +389,8 @@ const connectionToOutput = (connection: Connection) => ({
const oauthScopeCount = (scope: string | null | undefined): number | null =>
scope == null ? null : scope.split(/\s+/).filter(Boolean).length;

/** Lean projection for `connections.list`. Summarizes `oauthScope` to a count
* unless `verbose`, where the full grant string is included too. */
/** Lean projection for `connections.list`. Summarizes `oauthScope` and health
* diagnostics unless `verbose`, where the full grant string is included too. */
const connectionToListItem = (connection: Connection, verbose: boolean) => ({
owner: connection.owner,
name: String(connection.name),
Expand All @@ -404,7 +404,17 @@ const connectionToListItem = (connection: Connection, verbose: boolean) => ({
oauthClient: connection.oauthClient == null ? null : String(connection.oauthClient),
oauthClientOwner: connection.oauthClientOwner ?? null,
oauthScopeCount: oauthScopeCount(connection.oauthScope),
lastHealth: connection.lastHealth ?? null,
// Keep full probe diagnostics behind the explicit verbose opt-in.
lastHealth:
connection.lastHealth == null || verbose
? (connection.lastHealth ?? null)
: {
status: connection.lastHealth.status,
...(connection.lastHealth.identity !== undefined
? { identity: connection.lastHealth.identity }
: {}),
checkedAt: connection.lastHealth.checkedAt,
},
...(verbose ? { oauthScope: connection.oauthScope ?? null } : {}),
});

Expand Down
2 changes: 1 addition & 1 deletion packages/core/sdk/src/executor.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -438,7 +438,7 @@ describe("createExecutor", () => {

const listed = yield* executor.execute(
ToolAddress.make("executor.coreTools.connections.list"),
{ integration: "diagnostics" },
{ integration: "diagnostics", verbose: true },
);
expect(listed).toMatchObject({
connections: [
Expand Down
Loading