mirror of
https://github.com/whyour/qinglong.git
synced 2026-09-21 00:38:14 +08:00
224 lines
7.1 KiB
JavaScript
224 lines
7.1 KiB
JavaScript
const assert = require('node:assert/strict');
|
|
const { test } = require('node:test');
|
|
|
|
const {
|
|
InvalidPluginPackagePromptExecutionPlanError,
|
|
PLUGIN_PACKAGE_PROMPT_EXECUTION_PLAN_SCHEMA,
|
|
createPluginPackagePromptAdmissionBundle,
|
|
normalizePluginPackagePromptExecutionPlan,
|
|
pluginPackagePromptExecutionPlanDigest,
|
|
preparePluginPackagePromptExecution,
|
|
} = require('../dist/prompt/pluginPackagePromptExecution.js');
|
|
const {
|
|
pluginPackageAutomationPublicationDigest,
|
|
} = require('@qinglong/runtime-core/plugin-package-automation-publication');
|
|
|
|
function publication(overrides = {}) {
|
|
const unsigned = {
|
|
schema: 'qinglong/plugin-package-automation-publication@v1',
|
|
target: {
|
|
projectId: 'project-a',
|
|
packageName: 'package-a',
|
|
installationId: 'installation-a',
|
|
lockDigest: '1'.repeat(64),
|
|
generation: 3,
|
|
generationDigest: '2'.repeat(64),
|
|
materializedRevisionDigest: '3'.repeat(64),
|
|
},
|
|
state: 'active',
|
|
version: 1,
|
|
previousPublicationDigest: null,
|
|
lifecycleEventDigest: null,
|
|
definitions: {
|
|
workflows: [],
|
|
prompts: [
|
|
{
|
|
schema: 'qinglong/plugin-package-prompt-resource@v1',
|
|
id: 'summary',
|
|
name: 'Summary',
|
|
template: 'Summarize {{subject}} for {{audience}}.',
|
|
parameters: [
|
|
{ name: 'audience', required: false },
|
|
{ name: 'subject', required: true },
|
|
],
|
|
},
|
|
],
|
|
},
|
|
publishedAtMs: 1_000,
|
|
...overrides,
|
|
};
|
|
return {
|
|
...unsigned,
|
|
publicationDigest: pluginPackageAutomationPublicationDigest(unsigned),
|
|
};
|
|
}
|
|
|
|
function prepare(overrides = {}) {
|
|
const active = publication();
|
|
return preparePluginPackagePromptExecution({
|
|
publication: active,
|
|
expectedPublicationDigest: active.publicationDigest,
|
|
promptId: 'summary',
|
|
requestId: 'prompt-request-a',
|
|
traceId: 'trace-a',
|
|
requestedBySubject: { type: 'user', id: 'user-a' },
|
|
policyFence: { projectVersion: 1, bindingVersion: 1 },
|
|
parameters: { subject: 'QingLong 3.0' },
|
|
provider: 'openai-compatible',
|
|
model: 'model-a',
|
|
maxOutputTokens: 512,
|
|
temperature: 0.2,
|
|
plannedAtMs: 2_000,
|
|
deadlineAtMs: 62_000,
|
|
...overrides,
|
|
});
|
|
}
|
|
|
|
test('prepares a content-free immutable Prompt execution plan', () => {
|
|
const first = prepare();
|
|
const second = prepare();
|
|
assert.deepEqual(first, second);
|
|
assert.equal(first.plan.schema, PLUGIN_PACKAGE_PROMPT_EXECUTION_PLAN_SCHEMA);
|
|
assert.deepEqual(first.plan.output, { mode: 'live_only' });
|
|
assert.equal(
|
|
first.request.messages[0].content,
|
|
'Summarize QingLong 3.0 for .',
|
|
);
|
|
assert.match(first.plan.invocationId, /^ppi:[0-9a-f]{32}$/);
|
|
assert.match(first.plan.runId, /^ppr:[0-9a-f]{32}$/);
|
|
assert.match(first.plan.stepRunId, /^pps:[0-9a-f]{32}$/);
|
|
assert.match(first.plan.modelRequestDigest, /^sha256:[0-9a-f]{64}$/);
|
|
const durable = JSON.stringify(first.plan);
|
|
assert.equal(durable.includes('QingLong 3.0'), false);
|
|
assert.equal(durable.includes('Summarize '), false);
|
|
assert.equal(Object.isFrozen(first.plan), true);
|
|
});
|
|
|
|
test('binds explicit durable output retention and preserves legacy live-only replay', () => {
|
|
const durable = prepare({
|
|
output: {
|
|
mode: 'durable_artifact',
|
|
retentionPolicy: {
|
|
revision: 'edge-output-v1',
|
|
retentionMs: 86_400_000,
|
|
},
|
|
},
|
|
}).plan;
|
|
assert.equal(durable.output.mode, 'durable_artifact');
|
|
assert.match(durable.output.retentionPolicyDigest, /^[0-9a-f]{64}$/);
|
|
assert.notEqual(durable.planDigest, prepare().plan.planDigest);
|
|
assert.throws(
|
|
() =>
|
|
normalizePluginPackagePromptExecutionPlan({
|
|
...durable,
|
|
output: {
|
|
...durable.output,
|
|
retentionPolicyDigest: 'f'.repeat(64),
|
|
},
|
|
}),
|
|
InvalidPluginPackagePromptExecutionPlanError,
|
|
);
|
|
|
|
const current = prepare().plan;
|
|
const {
|
|
output: _output,
|
|
planDigest: _planDigest,
|
|
...legacyUnsigned
|
|
} = current;
|
|
const legacy = {
|
|
...legacyUnsigned,
|
|
planDigest: pluginPackagePromptExecutionPlanDigest(legacyUnsigned),
|
|
};
|
|
assert.equal(
|
|
normalizePluginPackagePromptExecutionPlan(legacy).output,
|
|
undefined,
|
|
);
|
|
});
|
|
|
|
test('binds parameter presence and content without recursively rendering values', () => {
|
|
const omitted = prepare();
|
|
const empty = prepare({
|
|
parameters: { subject: 'QingLong 3.0', audience: '' },
|
|
});
|
|
const literal = prepare({
|
|
parameters: { subject: '{{audience}}', audience: 'operators' },
|
|
});
|
|
assert.notEqual(omitted.plan.parameterDigest, empty.plan.parameterDigest);
|
|
assert.equal(
|
|
literal.request.messages[0].content,
|
|
'Summarize {{audience}} for operators.',
|
|
);
|
|
});
|
|
|
|
test('rejects stale publication, missing or widened parameters and unsafe budgets', () => {
|
|
const active = publication();
|
|
assert.throws(
|
|
() =>
|
|
preparePluginPackagePromptExecution({
|
|
...prepare().plan,
|
|
publication: active,
|
|
expectedPublicationDigest: 'f'.repeat(64),
|
|
promptId: 'summary',
|
|
requestId: 'request-b',
|
|
traceId: 'trace-b',
|
|
requestedBySubject: { type: 'user', id: 'user-b' },
|
|
policyFence: { projectVersion: 1, bindingVersion: 1 },
|
|
parameters: { subject: 'x' },
|
|
provider: 'provider-a',
|
|
model: 'model-a',
|
|
maxOutputTokens: 1,
|
|
plannedAtMs: 2_000,
|
|
deadlineAtMs: 3_000,
|
|
}),
|
|
InvalidPluginPackagePromptExecutionPlanError,
|
|
);
|
|
assert.throws(() => prepare({ parameters: {} }), /subject is required/);
|
|
assert.throws(
|
|
() => prepare({ parameters: { subject: 'x', extra: 'y' } }),
|
|
/undeclared name/,
|
|
);
|
|
assert.throws(
|
|
() => prepare({ deadlineAtMs: 2_000 + 5 * 60_000 + 1 }),
|
|
/deadline/,
|
|
);
|
|
assert.throws(() => prepare({ maxOutputTokens: 32_769 }), /maxOutputTokens/);
|
|
});
|
|
|
|
test('creates one model StepRun admission without persisting Prompt content', () => {
|
|
const prepared = prepare();
|
|
const bundle = createPluginPackagePromptAdmissionBundle(prepared.plan);
|
|
assert.equal(bundle.run.status, 'running');
|
|
assert.equal(bundle.run.version, 2);
|
|
assert.equal(bundle.run.eventSequence, 2);
|
|
assert.equal(bundle.run.triggerType, 'plugin_package_prompt');
|
|
assert.equal(bundle.stepMutation.stepRun.kind, 'model');
|
|
assert.equal(bundle.stepMutation.stepRun.status, 'ready');
|
|
assert.equal(bundle.stepMutation.expectedRunVersion, 1);
|
|
assert.equal(bundle.stepMutation.event.sequence, 2);
|
|
assert.equal(bundle.receipt.finalRunVersion, 2);
|
|
assert.equal(bundle.receipt.invocationId, prepared.plan.invocationId);
|
|
const durable = JSON.stringify(bundle);
|
|
assert.equal(durable.includes('QingLong 3.0'), false);
|
|
assert.equal(durable.includes('Summarize '), false);
|
|
});
|
|
|
|
test('normalizer rejects identity and digest drift', () => {
|
|
const plan = prepare().plan;
|
|
assert.throws(
|
|
() =>
|
|
normalizePluginPackagePromptExecutionPlan({
|
|
...plan,
|
|
runId: 'ppr:drift',
|
|
}),
|
|
InvalidPluginPackagePromptExecutionPlanError,
|
|
);
|
|
assert.throws(
|
|
() =>
|
|
normalizePluginPackagePromptExecutionPlan({
|
|
...plan,
|
|
parameterDigest: 'f'.repeat(64),
|
|
}),
|
|
InvalidPluginPackagePromptExecutionPlanError,
|
|
);
|
|
});
|