fix: default optional Swift protocol fields (#103493)

This commit is contained in:
Peter Steinberger
2026-07-10 08:02:49 +01:00
committed by GitHub
parent ed74d8dc7d
commit e169bfed7c
5 changed files with 819 additions and 890 deletions
+1
View File
@@ -25,6 +25,7 @@ Docs: https://docs.openclaw.ai
### Fixes
- **Swift protocol initializers:** default every schema-optional generated initializer parameter to `nil` so additive protocol fields no longer break SDK construction call sites.
- **OpenCode Go MiMo catalog:** stop exposing the deprecated `mimo-v2-omni` and `mimo-v2-pro` aliases that reject agent requests, and keep release validation on the active MiMo V2.5 routes. (#103311, #103329) Thanks @krissding.
- **OpenAI-compatible streamed tool calls:** execute complete native tool calls from streams that end with SSE `data: [DONE]` but omit `finish_reason`, while keeping transport EOF and visible-text cases fail-closed. (#98124, #97994) Thanks @SunnyShu0925.
- **xAI provider aliases:** preserve Grok 4.3 and Grok 4.5 thinking profiles, fast-model routing, and encrypted reasoning replay when models use the shipped `x-ai` provider alias instead of clamping valid thinking requests to `minimal`. (#103315)
File diff suppressed because it is too large Load Diff
@@ -3,40 +3,49 @@ import Testing
struct GatewayModelsCompatibilityTests {
@Test
func `plugin approval request params keeps reviewer devices additive`() {
func `optional fields stay additive around required fields`() {
let params = PluginApprovalRequestParams(
pluginid: nil,
title: "Install plugin",
description: "Review requested",
severity: nil,
toolname: nil,
toolcallid: nil,
alloweddecisions: nil,
sessionkey: nil,
turnsourcechannel: nil,
turnsourceto: nil,
turnsourceaccountid: nil,
turnsourcethreadid: nil,
timeoutms: nil,
twophase: nil)
description: "Review requested")
#expect(params.pluginid == nil)
#expect(params.approvalreviewerdeviceids == nil)
}
@Test
func `message action params keeps requester account additive`() {
func `optional fields stay additive before trailing required fields`() {
let params = MessageActionParams(
channel: "slack",
action: "member-info",
params: [:],
accountid: "default",
requestersenderid: "U123",
senderisowner: true,
sessionkey: nil,
sessionid: nil,
toolcontext: nil,
idempotencykey: "test")
#expect(params.accountid == nil)
#expect(params.requesteraccountid == nil)
}
@Test
func `strict literal model optional fields default to nil`() {
let result = PluginsSessionActionSuccessResult()
#expect(result.ok)
#expect(result.result == nil)
}
@Test
func `chat send canonical initializer stays unambiguous`() {
let params = ChatSendParams(
sessionkey: "main",
message: "hello",
idempotencykey: "test")
let legacyParams = ChatSendParams(
sessionkey: "main",
message: "hello",
fastmode: true,
idempotencykey: "test")
#expect(params.agentid == nil)
#expect(params.fastmodevalue == nil)
#expect(legacyParams.fastmode == true)
}
}
+1 -1
View File
@@ -1733,7 +1733,7 @@
"prompt:snapshots:check": "node --import tsx scripts/generate-prompt-snapshots.ts --check",
"prompt:snapshots:gen": "node --import tsx scripts/generate-prompt-snapshots.ts --write",
"prompt:snapshots:sync-codex-model": "node --import tsx scripts/sync-codex-model-prompt-fixture.ts",
"protocol:check": "pnpm protocol:gen && pnpm protocol:gen:swift && git diff --exit-code -- dist/protocol.schema.json apps/macos/Sources/OpenClawProtocol/GatewayModels.swift apps/shared/OpenClawKit/Sources/OpenClawProtocol/GatewayModels.swift",
"protocol:check": "pnpm protocol:gen && pnpm protocol:gen:swift && git diff --exit-code -- dist/protocol.schema.json apps/shared/OpenClawKit/Sources/OpenClawProtocol/GatewayModels.swift",
"protocol:gen": "node --import tsx scripts/protocol-gen.ts",
"protocol:gen:swift": "node --import tsx scripts/protocol-gen-swift.ts",
"proxy:coverage": "node scripts/run-node.mjs proxy coverage",
+1 -82
View File
@@ -41,73 +41,6 @@ const STRICT_LITERAL_STRUCTS = new Set([
"PluginsSessionActionFailureResult",
]);
const DEFAULTED_OPTIONAL_INIT_PARAM_ENTRIES: readonly [string, readonly string[]][] = [
["CrestodianChatResult", ["sensitive"]],
["SendParams", ["buffer", "filename", "contentType"]],
["SessionOperationEvent", ["agentId"]],
["SessionsCompactionListParams", ["agentId"]],
["SessionsCompactionGetParams", ["agentId"]],
["SessionsCompactionBranchParams", ["agentId"]],
["SessionsCompactionRestoreParams", ["agentId"]],
["SessionsSendParams", ["agentId"]],
["SessionsMessagesSubscribeParams", ["agentId"]],
["SessionsMessagesUnsubscribeParams", ["agentId"]],
["SessionsAbortParams", ["agentId"]],
["SessionsListParams", ["archived"]],
["SessionsPatchParams", ["agentId", "category", "archived", "pinned"]],
["SessionsResetParams", ["agentId"]],
[
"SessionsDeleteParams",
["agentId", "expectedSessionId", "expectedLifecycleRevision", "expectedSessionUpdatedAt"],
],
["SessionsCompactParams", ["agentId"]],
["SessionsResolveParams", ["allowMissing"]],
["SessionsUsageParams", ["agentId", "agentScope", "timeZone"]],
["ChatHistoryParams", ["agentId", "offset"]],
["ChatSendParams", ["agentId"]],
["ChatAbortParams", ["agentId", "preserveSideRuns"]],
["ChatInjectParams", ["agentId"]],
["ChatDeltaEvent", ["agentId"]],
["ChatFinalEvent", ["agentId"]],
["ChatAbortedEvent", ["agentId", "errorMessage"]],
["ChatErrorEvent", ["agentId"]],
["ArtifactsListParams", ["agentId"]],
["ArtifactsGetParams", ["agentId"]],
["ArtifactsDownloadParams", ["agentId"]],
["MessageActionParams", ["inboundTurnKind", "requesterAccountId"]],
["CronListParams", ["compact"]],
[
"CronJob",
[
"declarationKey",
"displayName",
"owner",
"configRevision",
"nextRunAtMs",
"lastRunAtMs",
"lastRunStatus",
"lastRunError",
"lastDelivered",
"lastDeliveryStatus",
"lastDeliveryError",
"lastFailureNotificationDelivered",
"lastFailureNotificationDeliveryStatus",
"lastFailureNotificationDeliveryError",
],
],
["CronAddParams", ["declarationKey", "displayName", "owner"]],
["CronRunLogEntry", ["errorReason", "failureNotificationDelivery"]],
["ExecApprovalRequestParams", ["requireDeliveryRoute", "suppressDelivery"]],
["PluginApprovalRequestParams", ["approvalReviewerDeviceIds"]],
["DevicePairSetupCodeResult", ["gatewayUrls"]],
["AgentSummary", ["thinkingLevels", "thinkingOptions", "thinkingDefault"]],
["ModelChoice", ["available"]],
];
const DEFAULTED_OPTIONAL_INIT_PARAMS: Record<string, Set<string>> = Object.fromEntries(
DEFAULTED_OPTIONAL_INIT_PARAM_ENTRIES.map(([name, params]) => [name, new Set(params)]),
);
const header = `// Generated by scripts/protocol-gen-swift.ts — do not edit by hand\n// swiftlint:disable file_length\nimport Foundation\n\npublic let GATEWAY_PROTOCOL_VERSION = ${PROTOCOL_VERSION}\npublic let GATEWAY_MIN_PROTOCOL_VERSION = ${MIN_CLIENT_PROTOCOL_VERSION}\n\nprivate struct GatewayAnyCodingKey: CodingKey, Hashable {\n let stringValue: String\n let intValue: Int?\n\n init?(stringValue: String) {\n self.stringValue = stringValue\n self.intValue = nil\n }\n\n init?(intValue: Int) {\n self.stringValue = String(intValue)\n self.intValue = intValue\n }\n}\n\npublic enum ErrorCode: String, Codable, Sendable {\n${Object.values(
ErrorCodes,
)
@@ -280,21 +213,13 @@ function stringEnumCases(schema: JsonSchema): string[] | undefined {
}
function swiftInitializerParam(params: {
structName: string;
key: string;
name: string;
schema: JsonSchema;
required: boolean;
allowStructuralNamed?: boolean;
}): string {
const type = swiftType(params.schema, true, params.allowStructuralNamed ?? true);
if (params.required) {
return `${params.name}: ${type}`;
}
const defaultNil =
params.key === "agentId" ||
(DEFAULTED_OPTIONAL_INIT_PARAMS[params.structName]?.has(params.key) ?? false);
return `${params.name}: ${type}?${defaultNil ? " = nil" : ""}`;
return params.required ? `${params.name}: ${type}` : `${params.name}: ${type}? = nil`;
}
function emitEnum(name: string, schema: JsonSchema): string {
@@ -362,8 +287,6 @@ function emitStruct(name: string, schema: JsonSchema): string {
const propName = safeName(key);
const req = required.has(key);
return ` ${swiftInitializerParam({
structName: name,
key,
name: propName,
schema: prop,
required: req,
@@ -454,8 +377,6 @@ function emitStruct(name: string, schema: JsonSchema): string {
const propName = swiftInitializerName(name, key);
const req = required.has(key);
return ` ${swiftInitializerParam({
structName: name,
key,
name: propName,
schema: prop,
required: req,
@@ -503,8 +424,6 @@ function emitStructCompatibilityInitializer(
return " fastmode: Bool?";
}
return ` ${swiftInitializerParam({
structName: name,
key,
name: propName,
schema: prop,
required: required.has(key),