mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-11-01 23:43:16 +01:00
refactor(server): delegate ACPX model admission to the runner
Keep product defaults separate from qualification metadata and preserve explicit Cursor and Codex ACP model selection. Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
1 parent
30875812d2
commit
6f4a5e9e2a
4 files changed
+36
-13
No files matched your search
@@ -33,6 +33,8 @@ TypeScript profile registry, provider-pack builder, server pack verifier, and
|
||||
Cursor installation code. `packages/paperclip-runner/acpx-profiles.json` now owns
|
||||
the release/profile declarations; generated TypeScript and named Rust structs
|
||||
share them. Model admission is explicit and separate from qualification examples.
|
||||
Server admission calls the same resolver. Existing Claude/Grok product defaults
|
||||
are named separately; Cursor and Codex ACP still require explicit selection.
|
||||
Cursor closure pins are generated from the existing distribution manifest.
|
||||
Build and typecheck reject declaration drift, including mismatches with the
|
||||
Cursor release attestation and installed dependencies.
|
||||
|
||||
@@ -138,7 +138,7 @@ import { httpAdapter } from "./http/index.js";
|
||||
import {
|
||||
DEFAULT_OPENCODE_RUNNER_MODEL,
|
||||
PaperclipRunnerProviderProfileError,
|
||||
QUALIFIED_ACPX_RUNNER_MODELS,
|
||||
DEFAULT_ACPX_RUNNER_MODELS,
|
||||
QUALIFIED_OPENCODE_RUNNER_VERSION,
|
||||
resolvePaperclipRunnerProviderProfile,
|
||||
} from "../services/native-runtime/provider-profile.js";
|
||||
@@ -490,19 +490,19 @@ const paperclipRunnerAdapter: ServerAdapterModule = {
|
||||
models: [
|
||||
...codexModels,
|
||||
{ id: DEFAULT_OPENCODE_RUNNER_MODEL, label: "OpenRouter · DeepSeek V4 Flash 0731" },
|
||||
{ id: QUALIFIED_ACPX_RUNNER_MODELS.claude, label: "Claude Sonnet 5" },
|
||||
{ id: DEFAULT_ACPX_RUNNER_MODELS.claude, label: "Claude Sonnet 5" },
|
||||
{ id: "global.anthropic.claude-sonnet-4-6", label: "Amazon Bedrock · Claude Sonnet 4.6 (global)" },
|
||||
],
|
||||
listModels: async () => [
|
||||
...await listCodexModels(),
|
||||
{ id: DEFAULT_OPENCODE_RUNNER_MODEL, label: "OpenRouter · DeepSeek V4 Flash 0731" },
|
||||
{ id: QUALIFIED_ACPX_RUNNER_MODELS.claude, label: "Claude Sonnet 5" },
|
||||
{ id: DEFAULT_ACPX_RUNNER_MODELS.claude, label: "Claude Sonnet 5" },
|
||||
{ id: "global.anthropic.claude-sonnet-4-6", label: "Amazon Bedrock · Claude Sonnet 4.6 (global)" },
|
||||
],
|
||||
refreshModels: async () => [
|
||||
...await refreshCodexModels(),
|
||||
{ id: DEFAULT_OPENCODE_RUNNER_MODEL, label: "OpenRouter · DeepSeek V4 Flash 0731" },
|
||||
{ id: QUALIFIED_ACPX_RUNNER_MODELS.claude, label: "Claude Sonnet 5" },
|
||||
{ id: DEFAULT_ACPX_RUNNER_MODELS.claude, label: "Claude Sonnet 5" },
|
||||
{ id: "global.anthropic.claude-sonnet-4-6", label: "Amazon Bedrock · Claude Sonnet 4.6 (global)" },
|
||||
],
|
||||
supportsLocalAgentJwt: false,
|
||||
|
||||
@@ -43,6 +43,24 @@ describe("host ACPX qualification admission", () => {
|
||||
it("does not alter existing qualified providers", () => {
|
||||
expect(resolveAcpxQualification({ ...provider, agent: "codex" } as typeof provider, authorize([]))).toBeUndefined();
|
||||
});
|
||||
it.each(["claude", "grok", "cursor"])("keeps %s model selection open to native verification", (acpxAgent) => {
|
||||
expect(resolvePaperclipRunnerProviderProfile({ provider: "acpx", acpxAgent, model: "explicit-new-model" }))
|
||||
.toMatchObject({ acpxAgent, model: "explicit-new-model" });
|
||||
});
|
||||
it("preserves exact Codex ACP admission and explicit selection", () => {
|
||||
expect(resolvePaperclipRunnerProviderProfile({ provider: "acpx", acpxAgent: "codex", model: "gpt-5.6-sol" }))
|
||||
.toMatchObject({ acpxAgent: "codex", model: "gpt-5.6-sol" });
|
||||
for (const model of [undefined, "", "different-model"]) {
|
||||
expect(() => resolvePaperclipRunnerProviderProfile({ provider: "acpx", acpxAgent: "codex", model }))
|
||||
.toThrow(expect.objectContaining({ code: "paperclip_runner_acpx_model_unqualified" }));
|
||||
}
|
||||
});
|
||||
it("preserves product defaults separately from qualification examples", () => {
|
||||
for (const [acpxAgent, model] of [["claude", "claude-sonnet-5"], ["grok", "grok-4.7"]]) {
|
||||
expect(resolvePaperclipRunnerProviderProfile({ provider: "acpx", acpxAgent }))
|
||||
.toMatchObject({ acpxAgent, model });
|
||||
}
|
||||
});
|
||||
it("binds the executor to host process environment rather than agent configuration", () => {
|
||||
const source = readFileSync(new URL("./native-session-executor.ts", import.meta.url), "utf8");
|
||||
expect(source).toContain("resolveAcpxQualification(input.execution.provider, process.env)");
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { resolveQualifiedAcpxProfile } from "../../vendor/paperclip-runner/index.js";
|
||||
import {
|
||||
isPaperclipRunnerProvider,
|
||||
PAPERCLIP_RUNNER_PERMISSION_CAPABILITIES,
|
||||
@@ -21,16 +22,16 @@ export const DEFAULT_OPENCODE_RUNNER_MODEL =
|
||||
"openrouter/deepseek/deepseek-v4-flash-0731" as const;
|
||||
export const CLAUDE_MANAGED_BETA_VERSION = "managed-agents-2026-04-01" as const;
|
||||
|
||||
export const QUALIFIED_ACPX_RUNNER_MODELS = {
|
||||
export const DEFAULT_ACPX_RUNNER_MODELS = {
|
||||
grok: "grok-4.7",
|
||||
claude: "claude-sonnet-5",
|
||||
codex: "gpt-5.6-sol",
|
||||
// Cursor models are explicit and provider-verified; there is no default.
|
||||
cursor: "",
|
||||
// These profiles require explicit configuration. Admission belongs to the runner.
|
||||
codex: null,
|
||||
cursor: null,
|
||||
} as const;
|
||||
|
||||
export type QualifiedPaperclipRunnerAcpxAgent =
|
||||
keyof typeof QUALIFIED_ACPX_RUNNER_MODELS;
|
||||
keyof typeof DEFAULT_ACPX_RUNNER_MODELS;
|
||||
type AdmittedPaperclipRunnerAcpxAgent = QualifiedPaperclipRunnerAcpxAgent | AcpxQualificationCandidate;
|
||||
|
||||
export type PaperclipRunnerProviderProfile =
|
||||
@@ -473,17 +474,19 @@ export function resolvePaperclipRunnerProviderProfile(
|
||||
"Paperclip Runner ACPX requires a qualified agent profile.",
|
||||
);
|
||||
}
|
||||
const qualifiedModel = QUALIFIED_ACPX_RUNNER_MODELS[acpxAgent];
|
||||
if (acpxAgent === "codex" && model !== qualifiedModel) {
|
||||
const selectedModel = model || DEFAULT_ACPX_RUNNER_MODELS[acpxAgent] || "";
|
||||
try {
|
||||
resolveQualifiedAcpxProfile(acpxAgent, selectedModel);
|
||||
} catch (error) {
|
||||
throw new PaperclipRunnerProviderProfileError(
|
||||
"paperclip_runner_acpx_model_unqualified",
|
||||
`Paperclip Runner ACPX ${acpxAgent} requires exact model ${qualifiedModel}.`,
|
||||
error instanceof Error ? error.message : "Paperclip Runner ACPX requires an admitted model.",
|
||||
);
|
||||
}
|
||||
return {
|
||||
provider: "acpx",
|
||||
backend: "acpx_runtime",
|
||||
model: model || qualifiedModel,
|
||||
model: selectedModel,
|
||||
acpxAgent,
|
||||
};
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user