mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 05:20:43 +00:00
fix(release): accept Docker OCI attestations and xAI reasoning defaults
This commit is contained in:
@@ -25,6 +25,7 @@ export {
|
||||
XAI_IMAGE_MODELS,
|
||||
} from "./model-definitions.js";
|
||||
export { isModernXaiModel, resolveXaiForwardCompatModel } from "./provider-models.js";
|
||||
export { applyXaiRuntimeModelCompat } from "./runtime-model-compat.js";
|
||||
export {
|
||||
applyXaiModelCompat,
|
||||
HTML_ENTITY_TOOL_CALL_ARGUMENTS_ENCODING,
|
||||
|
||||
@@ -221,6 +221,7 @@ describe("xai provider plugin", () => {
|
||||
model: createProviderModel({ id: "grok-4-1-fast" }),
|
||||
} as never),
|
||||
).toMatchObject({
|
||||
thinkingLevelMap: { off: null },
|
||||
compat: {
|
||||
toolSchemaProfile: "xai",
|
||||
nativeWebSearchTool: true,
|
||||
|
||||
@@ -4,7 +4,7 @@ import { defaultToolStreamExtraParams } from "openclaw/plugin-sdk/provider-strea
|
||||
import { jsonResult, readProviderEnvValue } from "openclaw/plugin-sdk/provider-web-search";
|
||||
import { Type } from "typebox";
|
||||
import {
|
||||
applyXaiModelCompat,
|
||||
applyXaiRuntimeModelCompat,
|
||||
buildXaiImageGenerationProvider,
|
||||
normalizeXaiModelId,
|
||||
resolveXaiTransport,
|
||||
@@ -194,7 +194,7 @@ export default defineSingleProviderPluginEntry({
|
||||
mode: "api-key" as const,
|
||||
};
|
||||
},
|
||||
normalizeResolvedModel: ({ model }) => applyXaiModelCompat(model),
|
||||
normalizeResolvedModel: ({ model }) => applyXaiRuntimeModelCompat(model),
|
||||
normalizeTransport: ({ provider, api, baseUrl }) =>
|
||||
resolveXaiTransport({ provider, api, baseUrl }),
|
||||
contributeResolvedModelCompat: ({ modelId, model }) =>
|
||||
|
||||
@@ -3,9 +3,9 @@ import type {
|
||||
ProviderRuntimeModel,
|
||||
} from "openclaw/plugin-sdk/plugin-entry";
|
||||
import { normalizeModelCompat } from "openclaw/plugin-sdk/provider-model-shared";
|
||||
import { applyXaiModelCompat } from "openclaw/plugin-sdk/provider-tools";
|
||||
import { normalizeOptionalLowercaseString } from "openclaw/plugin-sdk/text-runtime";
|
||||
import { resolveXaiCatalogEntry, XAI_BASE_URL } from "./model-definitions.js";
|
||||
import { applyXaiRuntimeModelCompat } from "./runtime-model-compat.js";
|
||||
|
||||
const XAI_MODERN_MODEL_PREFIXES = ["grok-3", "grok-4", "grok-code-fast"] as const;
|
||||
|
||||
@@ -26,7 +26,7 @@ export function resolveXaiForwardCompatModel(params: {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return applyXaiModelCompat(
|
||||
return applyXaiRuntimeModelCompat(
|
||||
normalizeModelCompat({
|
||||
id: definition.id,
|
||||
name: definition.name,
|
||||
|
||||
19
extensions/xai/runtime-model-compat.ts
Normal file
19
extensions/xai/runtime-model-compat.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { applyXaiModelCompat } from "openclaw/plugin-sdk/provider-tools";
|
||||
|
||||
type XaiRuntimeModelCompat = {
|
||||
compat?: unknown;
|
||||
thinkingLevelMap?: Partial<
|
||||
Record<"off" | "minimal" | "low" | "medium" | "high" | "xhigh", string | null>
|
||||
>;
|
||||
};
|
||||
|
||||
export function applyXaiRuntimeModelCompat<T extends XaiRuntimeModelCompat>(model: T): T {
|
||||
const withCompat = applyXaiModelCompat(model);
|
||||
return {
|
||||
...withCompat,
|
||||
thinkingLevelMap: {
|
||||
...withCompat.thinkingLevelMap,
|
||||
off: null,
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -585,6 +585,7 @@ describe("xai provider models", () => {
|
||||
api: "openai-responses",
|
||||
baseUrl: "https://api.x.ai/v1",
|
||||
reasoning: true,
|
||||
thinkingLevelMap: { off: null },
|
||||
input: ["text", "image"],
|
||||
contextWindow: 1_000_000,
|
||||
maxTokens: 64_000,
|
||||
|
||||
@@ -4,6 +4,11 @@ import { execFileSync } from "node:child_process";
|
||||
import process from "node:process";
|
||||
|
||||
const ATTESTATION_REFERENCE_TYPE = "attestation-manifest";
|
||||
const ATTESTATION_ARTIFACT_TYPE = "application/vnd.docker.attestation.manifest.v1+json";
|
||||
const ATTESTATION_MANIFEST_MEDIA_TYPES = new Set([
|
||||
"application/vnd.docker.distribution.manifest.v2+json",
|
||||
"application/vnd.oci.image.manifest.v1+json",
|
||||
]);
|
||||
const REQUIRED_PREDICATES = ["https://spdx.dev/Document", "https://slsa.dev/provenance/v1"];
|
||||
|
||||
export function imageRefForDigest(imageRef, digest) {
|
||||
@@ -39,6 +44,13 @@ function platformMatches(actual, expected) {
|
||||
);
|
||||
}
|
||||
|
||||
function isAttestationManifest(attestation) {
|
||||
if (attestation?.artifactType !== undefined) {
|
||||
return attestation.artifactType === ATTESTATION_ARTIFACT_TYPE;
|
||||
}
|
||||
return ATTESTATION_MANIFEST_MEDIA_TYPES.has(attestation?.mediaType);
|
||||
}
|
||||
|
||||
function parseJson(raw, label) {
|
||||
try {
|
||||
return JSON.parse(raw);
|
||||
@@ -85,11 +97,11 @@ export function collectDockerAttestationErrors(params) {
|
||||
const predicates = new Set();
|
||||
for (const descriptor of attestationDescriptors) {
|
||||
const attestation = inspectAttestation(descriptor.digest);
|
||||
if (attestation?.artifactType !== "application/vnd.docker.attestation.manifest.v1+json") {
|
||||
if (!isAttestationManifest(attestation)) {
|
||||
errors.push(
|
||||
`${imageRef}: ${platformLabel} attestation ${descriptor.digest} has unexpected artifactType ${JSON.stringify(
|
||||
`${imageRef}: ${platformLabel} attestation ${descriptor.digest} has unexpected manifest shape artifactType=${JSON.stringify(
|
||||
attestation?.artifactType,
|
||||
)}`,
|
||||
)} mediaType=${JSON.stringify(attestation?.mediaType)}`,
|
||||
);
|
||||
}
|
||||
for (const layer of attestation?.layers ?? []) {
|
||||
|
||||
@@ -74,7 +74,6 @@ describeLive("xai live", () => {
|
||||
{
|
||||
apiKey: XAI_KEY,
|
||||
maxTokens: 64,
|
||||
reasoning: "medium",
|
||||
},
|
||||
);
|
||||
|
||||
@@ -107,7 +106,6 @@ describeLive("xai live", () => {
|
||||
{
|
||||
apiKey: XAI_KEY,
|
||||
maxTokens: 128,
|
||||
reasoning: "medium",
|
||||
onPayload: (payload) => {
|
||||
capturedPayload = payload as Record<string, unknown>;
|
||||
},
|
||||
|
||||
@@ -51,6 +51,11 @@ function createAttestation(
|
||||
};
|
||||
}
|
||||
|
||||
function createAttestationWithoutArtifactType() {
|
||||
const { artifactType: _artifactType, ...attestation } = createAttestation();
|
||||
return attestation;
|
||||
}
|
||||
|
||||
describe("verify-docker-attestations", () => {
|
||||
it("resolves digest refs from tagged image refs", () => {
|
||||
expect(imageRefForDigest("ghcr.io/openclaw/openclaw:2026.4.26", imageDigest)).toBe(
|
||||
@@ -72,6 +77,17 @@ describe("verify-docker-attestations", () => {
|
||||
expect(errors).toEqual([]);
|
||||
});
|
||||
|
||||
it("accepts OCI attestation manifests without artifactType", () => {
|
||||
const errors = collectDockerAttestationErrors({
|
||||
imageRef: "ghcr.io/openclaw/openclaw:test",
|
||||
index: createIndex(),
|
||||
requiredPlatforms: [parsePlatform("linux/amd64")],
|
||||
inspectAttestation: () => createAttestationWithoutArtifactType(),
|
||||
});
|
||||
|
||||
expect(errors).toEqual([]);
|
||||
});
|
||||
|
||||
it("reports missing attestation manifests", () => {
|
||||
const index = createIndex();
|
||||
index.manifests = index.manifests.slice(0, 1);
|
||||
@@ -100,4 +116,20 @@ describe("verify-docker-attestations", () => {
|
||||
"ghcr.io/openclaw/openclaw:test: linux/amd64 missing predicate https://slsa.dev/provenance/v1",
|
||||
]);
|
||||
});
|
||||
|
||||
it("reports an unexpected attestation manifest shape", () => {
|
||||
const errors = collectDockerAttestationErrors({
|
||||
imageRef: "ghcr.io/openclaw/openclaw:test",
|
||||
index: createIndex(),
|
||||
requiredPlatforms: [parsePlatform("linux/amd64")],
|
||||
inspectAttestation: () => ({
|
||||
...createAttestation(),
|
||||
artifactType: "application/vnd.example.invalid",
|
||||
}),
|
||||
});
|
||||
|
||||
expect(errors).toEqual([
|
||||
`ghcr.io/openclaw/openclaw:test: linux/amd64 attestation ${attestationDigest} has unexpected manifest shape artifactType="application/vnd.example.invalid" mediaType="application/vnd.oci.image.manifest.v1+json"`,
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user