The SDK entry (src/host-integration-sdk.cts) is the single PUBLIC surface a host-plugin author imports: the negotiated schema + classification, the five adapters (declarative/imperative/model/hook/state), and the serialized handshake. Frozen so the public shape cannot be mutated. Everything else in gsd-core stays internal. tests/sdk-smoke.test.cjs imports ONLY from the SDK entry and builds a third-party host-plugin end-to-end (compose adapters + handshake + classify) — proving an external author can wire a host without gsd-core internals (#1683 AC). ESLint-ignore + inventory manifest kept in sync for the new tsc-emitted module.
This commit is contained in:
@@ -341,6 +341,7 @@
|
||||
"handshake-serialized.cjs",
|
||||
"hook-bus.cjs",
|
||||
"host-integration.cjs",
|
||||
"host-integration-sdk.cjs",
|
||||
"init-command-router.cjs",
|
||||
"init.cjs",
|
||||
"install-engine.cjs",
|
||||
|
||||
@@ -59,6 +59,7 @@ export default tseslint.config(
|
||||
'gsd-core/bin/lib/semver-compare.cjs',
|
||||
'gsd-core/bin/lib/host-integration.cjs',
|
||||
'gsd-core/bin/lib/handshake-serialized.cjs',
|
||||
'gsd-core/bin/lib/host-integration-sdk.cjs',
|
||||
'gsd-core/bin/lib/install-engine.cjs',
|
||||
'gsd-core/bin/lib/capability-loader.cjs',
|
||||
'gsd-core/bin/lib/capability-source.cjs',
|
||||
|
||||
58
src/host-integration-sdk.cts
Normal file
58
src/host-integration-sdk.cts
Normal file
@@ -0,0 +1,58 @@
|
||||
/**
|
||||
* Host-Integration SDK — the published public surface (ADR-1239 Phase E / #1683).
|
||||
*
|
||||
* External host-plugin authors import ONLY from this entry. It IS the contract:
|
||||
* everything it re-exports is public + versioned (PROTOCOL_VERSION governs the
|
||||
* set); everything else in gsd-core is internal. An SDK smoke test
|
||||
* (tests/sdk-smoke.test.cjs) builds a new host-plugin against this surface only
|
||||
* — proving an external author can wire a host without reading gsd-core internals.
|
||||
*
|
||||
* Surface: the negotiated schema + classification, the five adapters
|
||||
* (declarative/imperative/model/hook/state), and the serialized handshake.
|
||||
* Frozen so the public shape cannot be mutated by consumers.
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
||||
import hostIntegration = require('./host-integration.cjs');
|
||||
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
||||
import adapterDeclarative = require('./adapter-declarative.cjs');
|
||||
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
||||
import adapterImperative = require('./adapter-imperative.cjs');
|
||||
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
||||
import modelAdapter = require('./model-adapter.cjs');
|
||||
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
||||
import hookBus = require('./hook-bus.cjs');
|
||||
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
||||
import stateIo = require('./state-io.cjs');
|
||||
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
||||
import handshake = require('./handshake-serialized.cjs');
|
||||
|
||||
const SDK = Object.freeze({
|
||||
// ── Schema + protocol version ────────────────────────────────────────────
|
||||
PROTOCOL_VERSION: hostIntegration.PROTOCOL_VERSION,
|
||||
HOST_INTEGRATION_AXES: hostIntegration.HOST_INTEGRATION_AXES,
|
||||
INTERFACE_POINTS: hostIntegration.INTERFACE_POINTS,
|
||||
PROFILE_BASELINES: hostIntegration.PROFILE_BASELINES,
|
||||
|
||||
// ── Negotiation + classification ─────────────────────────────────────────
|
||||
negotiateHostCapabilities: hostIntegration.negotiateHostCapabilities,
|
||||
profileOf: hostIntegration.profileOf,
|
||||
degradationFor: hostIntegration.degradationFor,
|
||||
hookEventSurfaceFor: hostIntegration.hookEventSurfaceFor,
|
||||
shouldFlattenDispatch: hostIntegration.shouldFlattenDispatch,
|
||||
|
||||
// ── Embedding + engine adapters ──────────────────────────────────────────
|
||||
createDeclarativeAdapter: adapterDeclarative.createDeclarativeAdapter,
|
||||
createImperativeAdapter: adapterImperative.createImperativeAdapter,
|
||||
createModelAdapter: modelAdapter.createModelAdapter,
|
||||
createHookBus: hookBus.createHookBus,
|
||||
createStateIO: stateIo.createStateIO,
|
||||
|
||||
// ── Serialized handshake (out-of-process SDK hosts: pi / VS Code) ─────────
|
||||
HANDSHAKE_METHOD: handshake.HANDSHAKE_METHOD,
|
||||
buildHandshakeRequest: handshake.buildHandshakeRequest,
|
||||
handleHandshakeRequest: handshake.handleHandshakeRequest,
|
||||
});
|
||||
|
||||
export = SDK;
|
||||
67
tests/sdk-smoke.test.cjs
Normal file
67
tests/sdk-smoke.test.cjs
Normal file
@@ -0,0 +1,67 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* SDK smoke test — ADR-1239 Phase E / #1683 Slice 2.
|
||||
*
|
||||
* Proves the Host-Integration Interface is PUBLISHED: an external developer can
|
||||
* author + wire a new host-plugin against the SDK surface ONLY (no imports of
|
||||
* gsd-core internal paths). This test imports exclusively from the SDK entry —
|
||||
* if a host-plugin author needs a symbol that ISN'T here, the SDK is incomplete.
|
||||
*/
|
||||
|
||||
const { test } = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
|
||||
// EXTERNAL-AUTHOR CONTRACT: import ONLY from the published SDK entry.
|
||||
const SDK = require('../gsd-core/bin/lib/host-integration-sdk.cjs');
|
||||
|
||||
test('SDK exports the full public surface a host-plugin author needs', () => {
|
||||
assert.equal(typeof SDK.PROTOCOL_VERSION, 'number');
|
||||
assert.ok(SDK.HOST_INTEGRATION_AXES, 'negotiated schema (axes)');
|
||||
assert.ok(SDK.INTERFACE_POINTS, 'interface points');
|
||||
assert.ok(SDK.PROFILE_BASELINES, 'profile baselines');
|
||||
|
||||
const fns = [
|
||||
'negotiateHostCapabilities', 'profileOf', 'degradationFor',
|
||||
'hookEventSurfaceFor', 'shouldFlattenDispatch',
|
||||
'createDeclarativeAdapter', 'createImperativeAdapter',
|
||||
'createModelAdapter', 'createHookBus', 'createStateIO',
|
||||
'buildHandshakeRequest', 'handleHandshakeRequest',
|
||||
];
|
||||
for (const fn of fns) {
|
||||
assert.equal(typeof SDK[fn], 'function', `SDK must export function: ${fn}`);
|
||||
}
|
||||
assert.equal(typeof SDK.HANDSHAKE_METHOD, 'string');
|
||||
});
|
||||
|
||||
test('the SDK surface is frozen (immutable public contract)', () => {
|
||||
assert.equal(Object.isFrozen(SDK), true);
|
||||
});
|
||||
|
||||
test('an external author can build a host-plugin against the SDK surface ONLY', () => {
|
||||
// Compose a minimal host-plugin using EXCLUSIVELY the SDK entry — mirrors what
|
||||
// an external author writes. No require() of any internal path succeeds here.
|
||||
const model = SDK.createModelAdapter({ modelMode: 'passive' });
|
||||
const bus = SDK.createHookBus({ bus: 'engine' });
|
||||
const io = SDK.createStateIO({ io: 'filesystem' });
|
||||
const adapter = SDK.createImperativeAdapter({ runtime: 'my-third-party-host' });
|
||||
|
||||
// Negotiate this host's capabilities via the SDK handshake.
|
||||
const req = SDK.buildHandshakeRequest({ embeddingMode: 'imperative', runtime: 'node' });
|
||||
const result = SDK.handleHandshakeRequest(req);
|
||||
const profile = SDK.profileOf(result.effective);
|
||||
|
||||
assert.equal(model.mode, 'passive');
|
||||
assert.equal(bus.bus, 'engine');
|
||||
assert.equal(io.io, 'filesystem');
|
||||
assert.equal(adapter.kind, 'imperative');
|
||||
assert.equal(adapter.runtime, 'my-third-party-host');
|
||||
assert.equal(profile, 'programmatic-cli');
|
||||
assert.ok(result.effective && result.points, 'handshake yields a negotiated result');
|
||||
});
|
||||
|
||||
test('the SDK handshake round-trips for a declarative host authored externally', () => {
|
||||
const req = SDK.buildHandshakeRequest({ embeddingMode: 'declarative', commandSurface: 'slash-file' });
|
||||
const result = SDK.handleHandshakeRequest(req);
|
||||
assert.equal(SDK.profileOf(result.effective), 'declarative-cli');
|
||||
});
|
||||
Reference in New Issue
Block a user