npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@galust-ai/copilot-ui

v2.5.2

Published

A secure, modular Copilot integration kit for Conversation Result V1 and V2.

Readme

@galust-ai/copilot-ui — current 2.x

The supported turnkey and composable integration kit for a customer-owned Galust Copilot. A product can mount the turnkey Copilot with one validated configuration and trusted adapters, compose the same canonical controller with focused primitives, or consume only the headless protocol/runtime/server boundaries. The product continues to own identity, authorization, domain data, routes, effects, and product-specific renderers; Galust remains the durable conversation and Conversation Result authority.

For the release-matched architecture, capability/ownership matrix, and framework-neutral handoff, read docs/client-integration-reference.md. Existing 1.5 consumers should also follow docs/migration-v1.5-to-2.x.md. These guides use release-neutral current-2.x wording; verify and pin an exact public version through the npm registry for production.

Install

npm install @galust-ai/copilot-ui

The package supports React 18 and 19. Styles are opt-in and scoped below .galust-copilot-ui; the protocol, runtime, registry, server, and testing entries do not import CSS.

Choose the smallest entry point

| Need | Import | | --- | --- | | Turnkey Copilot with floating, panel, embedded, or page surface | /react + /styles.css | | Validated grouped configuration and defaults | /config | | Canonical controller, provider, commands, and focused selectors | /react/controller | | Normalized Sessions browser adapter, DTOs, and validation | /sessions | | V1/V2 DTOs, JSON Schemas, validation, response builders | /protocol | | SSE parsing, reducer, fetch transport, lifecycle callbacks | /runtime | | Semantic type IRI to local renderer mapping | /registry | | Conversation Result renderer only | /react/result + /styles/result.css | | Validated Conversation Result input controls | /react/inputs + /styles/result.css | | Presentation fallback and host-effect receipts | /react/presentations + /styles/result.css | | Themeable line, area, bar, and donut widgets | /react/charts + /styles/charts.css | | Message, composer, action, result, evidence, and run primitives | /react/primitives | | Optional thread, launcher, modal, and conversation shell | /react/shell | | All React exports for convenience | /react + /styles.css | | Framework-neutral secure customer BFF handler | /server | | Full/empty/invalid results, stream, contribution, and BFF harness | /testing | | Machine-readable public component inventory | /manifest | | Published contract provenance and public protocol artifacts | /protocol/contract-manifest.json | | Product Tool semantic-binding schema, examples, and provenance | /contracts/* |

The package root is equivalent to /protocol; it is React/CSS-free.

The package-internal assistant-ui bridge targets the exactly tested @assistant-ui/[email protected]. The public state boundary is now the Galust-owned controller and CopilotProvider; assistant-ui types remain internal and may evolve without changing the wire or customer API. The assistant-ui runtime is installed transitively by this package rather than exposed as a customer-managed peer. See docs/assistant-ui-compatibility.md for the ownership mapping, tested React matrix, and current Node engine caveat.

Start with the turnkey Copilot

The default integration is a turnkey conversation experience—not a recipe for customers to rebuild message state, streaming, continuations, Sessions, or surface behavior.

import "@galust-ai/copilot-ui/styles.css";
import { Copilot } from "@galust-ai/copilot-ui/react";
import { createFetchCopilotTransport } from "@galust-ai/copilot-ui/runtime";
import { createCopilotSessionsBrowserAdapter } from "@galust-ai/copilot-ui/sessions";

const transport = createFetchCopilotTransport("/api/copilot/stream");
const sessions = createCopilotSessionsBrowserAdapter({
  listUrl: "/api/copilot/sessions/list",
  historyUrl: "/api/copilot/sessions/history",
  commandUrl: "/api/copilot/sessions/command",
});

export function ProductCopilot() {
  return (
    <Copilot
      config={{
        transport: { endpoint: "/api/copilot/stream" },
        surface: { mode: "page", placement: "right" },
        sessions: { enabled: true },
        features: { conversationResults: true, suggestions: true },
        capabilities: {
          interactionResultVersions: ["2.0", "1.0"],
          presentations: [],
        },
        composer: { placeholder: "Ask about this workspace…" },
        locale: {
          locale: "en-GB",
          direction: "auto",
          messages: { "empty.title": "How can I help with this workspace?" },
        },
      }}
      adapters={{ transport, sessions }}
      context={[{ id: "workspace", label: "Current workspace" }]}
      registries={{
        renderReference: (reference) => renderAuthorizedReference(reference),
        renderOutput: (output) => renderStructuredOutput(output),
      }}
    />
  );
}

All four surfaces—floating, panel, embedded, and page—use the same controller instance and preserve draft, transcript, run, result, pending response, and session scope. Use /react/controller and the focused visual subpaths only when a deliberately custom layout is required. Do not build a second reducer or result store around the turnkey component.

The turnkey view shows safe, user-facing progress from existing SSE lifecycle events, reveals streamed and final-only answers progressively, and follows a growing answer while the reader is near the bottom. Scrolling up pauses that follow until the reader selects Latest. Reduced-motion mode shows the full available text without the reveal animation. The composer stays focused and editable during a response so the next question can be drafted; a second send is available when the current response finishes. Progress is transient and is not restored as durable run history.

Configuration is validated before use. Browser configuration cannot set a Galust credential, upstream URL, Product Use Case, trusted scope, permission, action target, route, or executable component. Those stay in the customer BFF and source-controlled registries.

The grouped configuration covers transport, Sessions, surface geometry, feature availability, declared client capabilities, composer behavior, theme tokens, locale resources, telemetry, diagnostics, accessibility, and bounded limits. Unknown keys and unsafe values fail before network work. Locale message keys are stable presentation overrides; omitted values keep the accessible English defaults. Common keys include composer.placeholder, empty.title, empty.description, status.working, action.tryAgain, sessions.title, sessions.searchPlaceholder, announce.responding, and announce.complete.

The optional context prop renders removable scope chips only. It is a user orientation affordance, not trusted context or authorization; the BFF must derive authoritative product and tenant scope independently. Attachments, voice input, feedback, and Sessions controls appear only when both the feature and the matching typed adapter are present.

Proxy Sessions without creating another persistence layer

Sessions remain Galust-backed conversation history. A customer BFF authenticates, scopes, filters, and proxies list, history, and command requests; it does not copy the transcript into a second store. Mount the reusable handler behind three static product routes matching the browser adapter above:

import {
  handleCopilotSessionsRequest,
  type CopilotSessionsServerOperation,
} from "@galust-ai/copilot-ui/server";

export function sessionsRoute(operation: CopilotSessionsServerOperation) {
  return (request: Request) => handleCopilotSessionsRequest(request, {
    operation,
    authenticate: requireProductUser,
    resolveContext: resolveTrustedProductContext,
    authorize: (context, requestedOperation, conversationId) =>
      mayUseConversation(context, requestedOperation, conversationId),
    list: (context, input) => listAuthorizedGalustSessions(context, input),
    history: (context, input) => loadAuthorizedGalustHistory(context, input),
    command: (context, input) => runAuthorizedGalustSessionCommand(context, input),
  });
}

// /api/copilot/sessions/list    -> sessionsRoute("list")
// /api/copilot/sessions/history -> sessionsRoute("history")
// /api/copilot/sessions/command -> sessionsRoute("command")

Each route fixes its operation server-side. The providers must derive product, tenant, user, and conversation scope from the authenticated session; IDs in the request are selectors within that authorized scope, never authority. The handler validates both incoming DTOs and provider output and fails closed when an operation, provider, or result is unavailable.

The bounded browser metadata DTO is published at @galust-ai/copilot-ui/sessions/galust-copilot-conversation-message-metadata.schema.json. It exposes only validated result/response fields; it is not the unbounded server storage metadata shape.

1. Mount the customer BFF

The browser must call a product-owned endpoint. Configure real authentication, authorization, server-only Galust credentials, and one static allowlisted use case. The browser cannot select those values.

import {
  createHistoryBackedInvocationValidator,
  handleCopilotProxyRequest,
  type CopilotProxyContext,
} from "@galust-ai/copilot-ui/server";

type AuthorizedCopilotContext = CopilotProxyContext & {
  authorizedConversationUid: string;
  userId: string;
};

const validateContinuation = createHistoryBackedInvocationValidator<AuthorizedCopilotContext>({
  loadAuthorizedConversationMessages: async (context) =>
    loadGalustConversationMessages(context.authorizedConversationUid),
});

export async function POST(request: Request) {
  return handleCopilotProxyRequest(request, {
    upstreamUrl: process.env.GALUST_ASK_URL!,
    useCase: "customer-copilot",

    authenticate: async (request) => requireProductUser(request),
    resolveContext: async (user, browserRequest) =>
      resolveAuthorizedProductContext(user, browserRequest.conversationId),
    authorize: async (context, operation) =>
      mayUseCopilot(context.userId, context.tenantId, operation),
    credentials: async () => process.env.GALUST_API_TOKEN ?? null,

    resolveUpstreamConversation: async (context, browserConversationId) =>
      resolveGalustConversationUid(context, browserConversationId),
    mapBrowserInputData: async (_context, browserInputData) => ({
      surface: browserInputData?.surface,
    }),
    buildTrustedInputData: async (context) => ({
      productContext: await loadAuthorizedDomainContext(context),
    }),
    buildUpstreamHeaders: async (context) => ({
      "x-isolation-context": JSON.stringify({ clientId: context.tenantId }),
    }),

    validateInvocation: validateContinuation,
    validateConversationResultResponse: validateContinuation,
  });
}

The composition hooks are deliberately constrained:

  • resolveUpstreamConversation maps a local browser reference to an authorized upstream UID. Returning null fails closed.
  • mapBrowserInputData allowlists non-authoritative browser hints. Raw inputData is dropped when trusted composition is enabled and no mapper is supplied.
  • buildTrustedInputData derives authoritative input on the server and wins over mapped browser keys.
  • buildUpstreamHeaders cannot override authorization, content type, accept, host, connection, transfer encoding, or content length.

For backward compatibility, a 1.2 caller using none of these four hooks keeps its accepted request-body behavior. Configure the hooks for product-ready integrations. A rejected provider causes no upstream or persistence call.

The history callback must load message records from the conversation already authorized for that user and tenant. The helper accepts offer evidence only from records whose sender_type is exactly llm; user-message metadata is never assistant offer evidence. Never pass browser-supplied transcript or context into the validator. The helper prefers a complete metadata.conversationResult, supports the deprecated identity-only conversationInteractionOffer only when no matching complete result exists, and rejects extra legacy IDs when both forms are present. This is an early BFF gate; Galust remains the final authority for the ordinary continuation turn. It is not an idempotency or distributed exactly-once mechanism.

2. Send the capability offer and consume SSE

import {
  createFetchCopilotTransport,
  runCopilotStream,
} from "@galust-ai/copilot-ui/runtime";

const transport = createFetchCopilotTransport("/api/copilot/stream");

const state = await runCopilotStream(
  transport,
  {
    message: "Show my checkout project",
    conversationId: localConversationId,
    clientCapabilities: {
      interactionResult: { versions: ["2.0", "1.0"] },
      presentations: [{
        profile: "https://customer.example/presentations/summary",
        versions: ["1.0"],
      }],
    },
  },
  {
    onEnvelope: (envelope) => appendToProductStreamLog(envelope),
    onStateChange: (nextState) => updateConversation(nextState),
    onResult: (result) => renderResult(result),
    onFailure: (message) => showRecoverableError(message),
  },
);

The runtime understands ready, start, token, V1 interaction, V2 result, final, done, error, and unknown envelopes. Invalid or absent rich data never removes the ordinary final-text fallback. Capability order is preference order; V2-enabled products offer ["2.0", "1.0"], while existing products can keep ["1.0"]. Presentation offers are independent compatibility metadata, never component selection or authorization.

Continue V2 input and action offers

V2 never exposes a tool, URL, route, or component target. Build one exact, attributable response and send it as the next user turn:

import {
  buildConversationResultActionResponse,
  buildConversationResultInputResponse,
} from "@galust-ai/copilot-ui/protocol";

const inputTurn = buildConversationResultInputResponse(result.resultId, input.id, value);
const actionTurn = buildConversationResultActionResponse(result.resultId, action.id);

The customer BFF must configure both validateInvocation for V1 and validateConversationResultResponse for V2. The history-backed helper accepts either continuation and checks exact result/item identity only against authorized sender_type: "llm" history. Galust remains the final governed authority. A mixed V1/V2 request, unoffered item, extra property, browser target, or missing validator is denied before the upstream call.

Package support and managed-runtime availability are separate release facts. The current 2.x package can render an offered V2 action and build the canonical response, but the current Galust managed runtime keeps V2 action execution fail-closed until private authority retrieval is deployed. V2 input responses and the existing V1 governed continuation remain available. Verify the managed-runtime release before treating a V2 write path as deployable; do not replace the denial with a browser-side target or direct product write.

The default V2 renderer handles input, choice, multi_choice, review, confirmation, attachment, and correction. Its bounded JSON Schema subset supports strings and long text, booleans, numbers and integers, enums, multi-enums, date/time/date-time/URI formats, arrays of enums, and objects up to two nested levels. Unsupported or unsafe shapes remain visible and fail closed. Attachment requests accept a string or array-of-string response schema and use the configured attachment adapter to resolve files to opaque product-owned IDs before building the continuation; raw files never enter the continuation DTO. The server still authorizes those IDs and the requested response.

Restore durable results from conversation history

For negotiated turns, Galust persists the exact canonical result with the corresponding assistant message before exposing the live interaction event. V2 does the same before exposing the live result event. Reload the existing conversation-message history; do not replay Ask or create a customer-side shadow result store.

import { parseConversationResult } from "@galust-ai/copilot-ui/protocol";

const restored = message.senderType === "llm"
  ? parseConversationResult(message.metadata?.conversationResult)
  : null;

Keep the containing message text as the fallback when metadata is absent or invalid. The normalized Sessions DTO exposes only conversationResult and conversationResultResponse; internal trace, authority, and unrelated server metadata never cross the browser boundary. A host using assistant-ui can map both live and restored results to the same { type: "data", name: "conversation-result", data: result } part and render that part with ConversationResultView. Product-defined data-part names may differ; the semantic DTO must not.

Map neutral meaning to product components

The registry is a source-controlled allowlist in the customer frontend. Galust does not receive component, route, module, or catalog names.

import { createSemanticRendererRegistry } from "@galust-ai/copilot-ui/registry";

const registry = createSemanticRendererRegistry(renderUnknownReference);
registry.registerReference(
  "https://customer.example/entity-types/project",
  (reference) => <ProjectCard projectId={reference.entityId} />,
);
registry.registerOutput({
  schemaId: "https://customer.example/schemas/project-health/v1",
  render: (output) => <ProjectHealth data={output.value} />,
});
registry.registerPresentation({
  profile: "https://customer.example/presentations/summary",
  major: 1,
  render: (presentation) => <SummaryLayout presentation={presentation} />,
});

Output precedence is exact schema ID, then semantic type plus media type, then semantic type, then the generic structured-data fallback. Unknown references, outputs, presentations, inputs, and effects remain visible through safe local fallbacks and never trigger dynamic imports.

Use ConversationResultView from /react/result, ConversationInput from /react/inputs, and ConversationPresentation from /react/presentations. If the host already renders the stored assistant text, always pass showMessage={false}. Suggestions append their message as an ordinary user turn. Input and action responses use the V2 builders above. Product data hydration and host effects stay in customer-owned callbacks; the package does not fetch entities or execute routes.

The chart entry exposes native Recharts line, area, bar, and donut widgets. It does not claim Vega-Lite compatibility. Hosts bind validated structured outputs to source-controlled chart mappings and keep a table/text alternative.

Local product effects are deliberately separate from governed actions:

import { createHostEffectController } from "@galust-ai/copilot-ui/runtime";

const effects = createHostEffectController({
  "https://customer.example/effects/open-project": async (request, signal) => {
    await productRouter.openProject(String(request.payload.projectId), { signal });
    return { label: "Project opened" };
  },
});

Only exact source-controlled type IRIs execute. Unknown values fail closed; cancellation is supported; receipts expose only bounded status and label data. An effect never creates conversationResultResponse and cannot replace the normal BFF/orchestrator action path.

For this package release, use the shipped persisted-result metadata schema with its canonical Conversation Result schema and the complete message fixture as machine-readable references. They are exported from @galust-ai/copilot-ui/protocol/conversation-message-result-metadata.schema.json and @galust-ai/copilot-ui/protocol/galust-conversation-interaction.schema.json, with the fixture at @galust-ai/copilot-ui/protocol/conversation-message-with-result.v1.json. The release-matched behavior and ownership walkthrough is packaged at docs/client-integration-reference.md. The public website may link to these imports only after registry readback proves that the installed release contains them.

3. Validate and retain the exact protocol DTO

ConversationResult includes a message and six typed semantic categories: mentions, actions, nextSteps, interactions, artifacts, and diagnostics. /protocol exports their TypeScript types, the synchronized JSON Schemas, and validation functions.

import {
  parseConversationResult,
  conversationResultSchema,
  semanticContributionSchema,
  type ConversationResult,
  type ConversationAction,
  type ConversationInteraction,
} from "@galust-ai/copilot-ui/protocol";

const result: ConversationResult | null = parseConversationResult(envelope.data);

The validators are generated from Galust shared contracts. contractManifest contains their checksums, and the package test suite can compare them with a shared-contract checkout through GALUST_SHARED_CONTRACTS_DIR. The parseSemanticContribution() helper validates the V1 contribution contract. The exported V2 contribution and carrier JSON Schemas are machine-readable validation artifacts; they are not parsed by that V1 helper.

How Galust produces entity mentions

The browser, this package, and the customer BFF do not manufacture semantic mentions. Galust has two trusted server-side producer paths, and both end in the same ConversationResult DTO:

  1. A cooperative MCP tool may return native semantic contribution metadata.
  2. An ordinary OpenAPI, MCP, or third-party Product Tool may keep returning its existing records. A Galust operator configures a declarative semantic entity binding on that Product Tool. During a negotiated run, Galust core creates private, run-scoped candidates and the Product Use Case may explicitly select a relevant candidate by opaque reference.

The second path is internal to Galust execution. Candidate references and the core selection tool are never browser DTOs, package APIs, or values the BFF should synthesize. Galust resolves entity identity and tool/execution provenance, applies the Product Use Case interaction profile, and emits only the selected, allowed mention. Calling a bound tool does not automatically render every returned record.

Product Tool bindings prefer MCP structuredContent. JSON text fallback must be enabled in that binding and accepts only a complete JSON document—never assistant prose or JSON embedded in explanatory text. See docs/client-integration-reference.md for the public ownership and binding boundary.

The native metadata path remains useful when the tool intentionally owns the semantic result itself:

An MCP product tool can attach contribution bodies to the standard tool-result _meta object with the package helper. The tool does not claim its own source; the orchestrator attaches the observed runtime tool identity and execution ID.

import { buildMcpSemanticContributionMeta } from "@galust-ai/copilot-ui/protocol";

return {
  content: [{ type: "text", text: "Found the project." }],
  structuredContent: { project },
  _meta: buildMcpSemanticContributionMeta([{
    version: "1.0",
    mentions: [{
      id: `mention-${project.id}`,
      entity: {
        type: "https://customer.example/entity-types/project",
        id: project.id,
      },
      relationship: "primary-result",
      label: project.name,
    }],
  }]),
};

The metadata key is https://galust.ai/meta/semantic-contributions. The orchestrator ignores similar values in assistant prose and browser input. Ordinary structuredContent creates candidates only when the owning Product Tool has an explicit semantic entity binding; it is never treated as native contribution metadata.

4. Map semantics to product-owned UI

The orchestrator emits stable semantic values, never component names. Register local renderers by absolute entity type IRI and keep a text/reference fallback.

import { createSemanticRendererRegistry } from "@galust-ai/copilot-ui/registry";

const renderers = createSemanticRendererRegistry((mention) => (
  <span>{mention.label ?? mention.entity.id}</span>
));

renderers.registerMention(
  "https://cloudbrowser.io/entity-types/project",
  (mention) => <ProjectPreview projectId={mention.entity.id} />,
);

The local preview loads current data through the product's normal authorized API. Treat the mention as a reference, not a record snapshot or permission. Unknown values are never routes, imports, component names, credentials, or executable instructions.

5. Render inline and invoke offered actions or requested input

import "@galust-ai/copilot-ui/styles/result.css";
import { ConversationResultView } from "@galust-ai/copilot-ui/react/result";
import {
  buildActionInvocation,
  buildInteractionInvocation,
  type ConversationInteraction,
} from "@galust-ai/copilot-ui/protocol";

function RequestedInteraction({
  interaction,
  onSubmit,
}: {
  interaction: ConversationInteraction;
  onSubmit: (response: unknown) => void;
}) {
  // Render only supported JSON Schema shapes with the product's accessible UI.
  // This example leaves choice/input/review details to the product.
  return <ProductInteractionForm interaction={interaction} />;
}

<div className="galust-copilot-ui">
  <ConversationResultView
    result={result}
    showMessage={false}
    renderMention={(mention) => renderers.renderMention(mention)}
    onAction={(actionId) =>
      postToCustomerBff(buildActionInvocation(result.resultId, actionId))
    }
    renderInteraction={(interaction) => (
      <RequestedInteraction
        interaction={interaction}
        onSubmit={(response) =>
          postToCustomerBff(
            buildInteractionInvocation(result.resultId, interaction.id, response),
          )
        }
      />
    )}
  />
</div>

showMessage={false} is appropriate when the host already renders assistant text. An action invocation carries the source resultId; an interaction response carries resultId, interactionId, and its response. The BFF and use case must revalidate the stored offer and authorization. A next step is advisory unless it references an offered action selected by the user.

Requested interactions are explicit; confirmation has two meanings

ConversationInteraction is a published V1 DTO and JSON Schema shape. Its required fields are id, kind, prompt, and response; kind is one of input, choice, review, or confirmation. The response carries the JSON Schema plus required, missing, and declined behavior. Use the exported Conversation Result schema or ConversationInteraction type instead of guessing the shape.

actions[].confirmation and interactions[].kind: "confirmation" are not the same thing. The former is the server-governed confirmation posture of an offered action. The latter is an explicit request for the host to collect an answer. actions[].confirmation does not automatically create an interaction, a button, or a client-side side effect.

The client offer requests the complete result snapshot on a successful turn. The Product Use Case interaction profile is an allowlist, not a generator: it permits trusted result values but does not itself create actions, requested interactions, or UI. When a permitted interactions[] record is actually present, renderInteraction is the host projection point. It must render an accessible product control or a safe unsupported-schema fallback, and use buildInteractionInvocation() through the normal BFF continuation. The package never selects a product component or authorizes a domain operation.

Charts

Charts are reusable presentation primitives, not model-selected components. The product renderer maps authorized domain data into a typed local spec.

import "@galust-ai/copilot-ui/styles/charts.css";
import { CopilotChart } from "@galust-ai/copilot-ui/react/charts";

<div className="galust-copilot-ui">
  <CopilotChart spec={{
    kind: "line",
    title: "Successful sessions",
    categoryKey: "day",
    data: [{ day: "Mon", sessions: 12 }, { day: "Tue", sessions: 18 }],
    series: [{ key: "sessions", label: "Sessions" }],
  }} />
</div>

Available kinds are line, area, bar, and donut. Override Galust theme variables on .galust-copilot-ui to match customer branding.

Use the test harness

import {
  createFixtureTransport,
  createReferenceProxyConfig,
  fullConversationResultFixture,
  invalidConversationResultFixture,
  semanticContributionFixture,
} from "@galust-ai/copilot-ui/testing";

The fixtures cover all semantic categories, an invalid executable-looking action, a trusted contribution, and a complete stream. The reference proxy config supplies safe test defaults but still requires an authentication provider. Never use its test credential in production.

What the product still owns

  • the customer BFF deployment and framework adapter;
  • user/session authentication, tenant authorization, and Galust secret storage;
  • local-to-Galust conversation mapping plus customer-owned tracing and local retention policy; Galust owns persistence of canonical assistant results;
  • authorized domain APIs and Product Tool result data;
  • product-specific entity renderers, navigation, and data fetching;
  • configuring Product Tool semantic entity bindings when unchanged tools should supply entity candidates;
  • selecting/configuring a Product Use Case and semantic profile that support Conversation Result V2, while retaining V1 compatibility where needed.

The package provides reusable building blocks for each boundary; it does not invent product policy or domain records.

A2UI and assistant-ui

Conversation Result V1/V2 is Galust's frontend-neutral semantic protocol. A2UI is not required and no A2UI header, catalog sync, or endpoint is part of this package. A product may use assistant-ui for its chat shell while using this package for protocol, BFF, runtime, semantic rendering, and components.

Integration completion checklist

  • Browser calls only the customer BFF and offers ordered interactionResult: { versions: ["2.0", "1.0"] }; malformed or unsupported offers are ignored and fall back to ordinary text.
  • BFF providers authenticate, resolve, authorize, credential, compose trusted context, and validate invocations fail-closed.
  • Runtime renders ordinary text when semantic content is absent or invalid.
  • History retains message metadata and restores valid canonical results through the same renderer used for live interaction events.
  • Product registry maps known absolute type IRIs to local renderers and safely falls back for unknown values.
  • Only previously offered actions/interactions can be invoked.
  • Relevant package fixtures run in the product test suite.
  • Product Use Case and authorized tools/domain resolvers produce the semantic values; no IDs are inferred from assistant prose.
  • For ordinary tool records, Galust owns candidate creation and selection; browser/BFF code never invents candidate references, entity identity, or provenance.

SDK release policy

Compatible SDK bugfixes/performance corrections produce patch releases; SDK features produce minor releases; incompatible public API changes produce major releases. The current commit parser requires a BREAKING CHANGE: footer for a major release. Mixed/squash commits must describe the SDK's impact accurately.

The package release adapter selects the same package/shared Git history for version calculation and release notes. Unrelated app history, ordinary merge wrappers and explicit skip-release commits are excluded. Shared publishing paths remain conservative and are checked against both workflow triggers. The main stylesheet scans package/shared sources, rather than all app pages.

Git inspection requires full history and remerge-diff support (Git 2.36+). Missing history, unsupported merge forms or unrepresented merge-only SDK changes stop calculation. Record merge-resolution SDK impact in a subsequent ordinary SDK commit; a reverted follow-up does not satisfy this requirement. Root manifest/lockfile trigger coverage remains outside this boundary; do not broaden eligibility to unrelated app dependency features.

Run npm run test:release from this package for real-Git, configured-plugin and CSS regressions. The existing package test runs them before release CI can publish. Inspect the archive with npm pack --dry-run from the package directory. Published versions/tags remain immutable. DEV-2272 implementation still requires code review and approved merge before this guard is active.