* feat(#1680): ADR-1239 Phase C-1 — model adapter seam (passive + active) [AC3] Phase 3 slice 3 (AC3). Two model-layer adapters selected by the negotiated modelMode axis (host-integration.cts): - passive: formalizes today's tier routing from src/model-resolver.cts — resolveModel delegates straight to resolveModelForTier (byte-for-behavior). This is the CLI runtimes (claude/gemini/codex/opencode/cursor/...): GSD injects prompts / a per-agent model field. - active: a host-supplied sendRequest seam (VS Code vscode.lm / pi providers) — GSD calls the model through the host. Ships as a fail-closed seam (throws until a provider is bound); Phase 5 wires a concrete provider. createModelAdapter({modelMode}, {sendRequest?}) — factory gating throws on invalid mode. Proactive CI gates applied: ADR-457 eslint ignores entry + INVENTORY-MANIFEST cli_modules entry + comment-wording audited for the injection-scan substring trap. * chore(changeset): add Changed fragment for model adapter seam (#1680)
This commit is contained in:
78
src/model-adapter.cts
Normal file
78
src/model-adapter.cts
Normal file
@@ -0,0 +1,78 @@
|
||||
/**
|
||||
* Model adapter seam (ADR-1239 Phase C-1, AC3 / #1680).
|
||||
*
|
||||
* Two model-layer adapters selected by the negotiated `modelMode` axis
|
||||
* (host-integration.cts):
|
||||
*
|
||||
* - `passive` — GSD can only inject prompts / a per-agent `model` field (the
|
||||
* CLI runtimes: claude/gemini/codex/opencode/cursor/…). Formalizes today's
|
||||
* tier routing from src/model-resolver.cts: `resolveModel` delegates
|
||||
* straight to `resolveModelForTier`, so passive reproduces current behavior
|
||||
* byte-for-behavior.
|
||||
* - `active` — the host exposes a provider `sendRequest` (VS Code `vscode.lm`,
|
||||
* pi providers). GSD calls the model through the host. Ships here as a SEAM:
|
||||
* a host-supplied `sendRequest` slot, fail-closed until a real consumer
|
||||
* binds it (Phase 5 / #1682).
|
||||
*
|
||||
* Minimal (per ADR-1239 open wire-shape question): one factory, two shapes
|
||||
* discriminated by `mode`. Concrete provider protocol (request/response shape)
|
||||
* is fixed when a real active host lands in Phase 5.
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
||||
import modelResolver = require('./model-resolver.cjs');
|
||||
|
||||
export type ModelMode = 'passive' | 'active';
|
||||
|
||||
export interface ModelAdapter {
|
||||
readonly mode: ModelMode;
|
||||
}
|
||||
|
||||
export interface PassiveModelAdapter extends ModelAdapter {
|
||||
readonly mode: 'passive';
|
||||
/** Resolve a model id for a tier. Delegates to model-resolver's tier routing. */
|
||||
resolveModel(args: { cwd: string; agentType: string; attempt?: number }): string;
|
||||
}
|
||||
|
||||
export interface ActiveModelAdapter extends ModelAdapter {
|
||||
readonly mode: 'active';
|
||||
/** Host-supplied model-call primitive. Throws (fail-closed) if not bound. */
|
||||
sendRequest(req: unknown): unknown;
|
||||
}
|
||||
|
||||
export interface CreateModelAdapterOptions {
|
||||
/** Required for `active`: the host's model-call primitive. Ignored for `passive`. */
|
||||
sendRequest?: (req: unknown) => unknown;
|
||||
}
|
||||
|
||||
export function createModelAdapter(
|
||||
{ modelMode }: { modelMode: ModelMode },
|
||||
options: CreateModelAdapterOptions = {},
|
||||
): ModelAdapter {
|
||||
if (modelMode !== 'passive' && modelMode !== 'active') {
|
||||
throw new TypeError(`createModelAdapter: modelMode must be 'passive' | 'active' (got ${JSON.stringify(modelMode)})`);
|
||||
}
|
||||
if (modelMode === 'passive') {
|
||||
return Object.freeze({
|
||||
mode: 'passive' as const,
|
||||
resolveModel({ cwd, agentType, attempt }: { cwd: string; agentType: string; attempt?: number }): string {
|
||||
return modelResolver.resolveModelForTier(cwd, agentType, attempt);
|
||||
},
|
||||
});
|
||||
}
|
||||
// active: bind the host's sendRequest, fail-closed if absent.
|
||||
const sendRequest = options.sendRequest;
|
||||
return Object.freeze({
|
||||
mode: 'active' as const,
|
||||
sendRequest(req: unknown): unknown {
|
||||
if (typeof sendRequest !== 'function') {
|
||||
throw new Error(
|
||||
'ActiveModelAdapter.sendRequest: no host provider bound — the active model seam ' +
|
||||
'requires a sendRequest primitive from the host (Phase 5 wires a concrete provider).',
|
||||
);
|
||||
}
|
||||
return sendRequest(req);
|
||||
},
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user