@@ -1560,6 +1704,8 @@ impl<'a> SessionRpcTasks<'a> {
Ok(serde_json::from_value(_value)?)
}
+ /// Calls `session.tasks.sendMessage`.
+ ///
/// Wire method: `session.tasks.sendMessage`.
///
///
@@ -1591,6 +1737,8 @@ pub struct SessionRpcTools<'a> {
}
impl<'a> SessionRpcTools<'a> {
+ /// Calls `session.tools.handlePendingToolCall`.
+ ///
/// Wire method: `session.tools.handlePendingToolCall`.
pub async fn handle_pending_tool_call(
&self,
@@ -1617,7 +1765,13 @@ pub struct SessionRpcUi<'a> {
}
impl<'a> SessionRpcUi<'a> {
+ /// Calls `session.ui.elicitation`.
+ ///
/// Wire method: `session.ui.elicitation`.
+ ///
+ /// # Returns
+ ///
+ /// The elicitation response (accept with form values, decline, or cancel)
pub async fn elicitation(
&self,
params: UIElicitationRequest,
@@ -1632,6 +1786,8 @@ impl<'a> SessionRpcUi<'a> {
Ok(serde_json::from_value(_value)?)
}
+ /// Calls `session.ui.handlePendingElicitation`.
+ ///
/// Wire method: `session.ui.handlePendingElicitation`.
pub async fn handle_pending_elicitation(
&self,
@@ -1658,6 +1814,8 @@ pub struct SessionRpcUsage<'a> {
}
impl<'a> SessionRpcUsage<'a> {
+ /// Calls `session.usage.getMetrics`.
+ ///
/// Wire method: `session.usage.getMetrics`.
///
///
@@ -1685,6 +1843,8 @@ pub struct SessionRpcWorkspaces<'a> {
}
impl<'a> SessionRpcWorkspaces<'a> {
+ /// Calls `session.workspaces.getWorkspace`.
+ ///
/// Wire method: `session.workspaces.getWorkspace`.
pub async fn get_workspace(&self) -> Result {
let wire_params = serde_json::json!({ "sessionId": self.session.id() });
@@ -1699,6 +1859,8 @@ impl<'a> SessionRpcWorkspaces<'a> {
Ok(serde_json::from_value(_value)?)
}
+ /// Calls `session.workspaces.listFiles`.
+ ///
/// Wire method: `session.workspaces.listFiles`.
pub async fn list_files(&self) -> Result {
let wire_params = serde_json::json!({ "sessionId": self.session.id() });
@@ -1710,6 +1872,8 @@ impl<'a> SessionRpcWorkspaces<'a> {
Ok(serde_json::from_value(_value)?)
}
+ /// Calls `session.workspaces.readFile`.
+ ///
/// Wire method: `session.workspaces.readFile`.
pub async fn read_file(
&self,
@@ -1725,6 +1889,8 @@ impl<'a> SessionRpcWorkspaces<'a> {
Ok(serde_json::from_value(_value)?)
}
+ /// Calls `session.workspaces.createFile`.
+ ///
/// Wire method: `session.workspaces.createFile`.
pub async fn create_file(&self, params: WorkspacesCreateFileRequest) -> Result<(), Error> {
let mut wire_params = serde_json::to_value(params)?;
diff --git a/scripts/codegen/csharp.ts b/scripts/codegen/csharp.ts
index 3e532bd84..969146871 100644
--- a/scripts/codegen/csharp.ts
+++ b/scripts/codegen/csharp.ts
@@ -71,6 +71,10 @@ function escapeXml(text: string): string {
return text.replace(/&/g, "&").replace(//g, ">");
}
+function escapeXmlAttribute(text: string): string {
+ return escapeXml(text).replace(/"/g, """).replace(/'/g, "'");
+}
+
/** Ensures text ends with sentence-ending punctuation. */
function ensureTrailingPunctuation(text: string): string {
const trimmed = text.trimEnd();
@@ -92,6 +96,80 @@ function xmlDocComment(description: string | undefined, indent: string): string[
];
}
+function xmlDocElement(tagName: string, description: string | undefined, indent: string): string[] {
+ if (!description) return [];
+ const escaped = ensureTrailingPunctuation(escapeXml(description.trim()));
+ const lines = escaped.split(/\r?\n/);
+ if (lines.length === 1) {
+ return [`${indent}/// <${tagName}>${lines[0]}${tagName}>`];
+ }
+ return [
+ `${indent}/// <${tagName}>`,
+ ...lines.map((line) => `${indent}/// ${line}`),
+ `${indent}/// ${tagName}>`,
+ ];
+}
+
+function xmlDocNamedElement(
+ tagName: string,
+ name: string,
+ description: string | undefined,
+ indent: string,
+ escapeDescription = true
+): string[] {
+ if (!description) return [];
+ const preparedDescription = escapeDescription ? escapeXml(description.trim()) : description.trim();
+ const lines = ensureTrailingPunctuation(preparedDescription).split(/\r?\n/);
+ const escapedName = escapeXmlAttribute(name);
+ if (lines.length === 1) {
+ return [`${indent}/// <${tagName} name="${escapedName}">${lines[0]}${tagName}>`];
+ }
+ return [
+ `${indent}/// <${tagName} name="${escapedName}">`,
+ ...lines.map((line) => `${indent}/// ${line}`),
+ `${indent}/// ${tagName}>`,
+ ];
+}
+
+function rpcResultDescription(method: RpcMethod, resultSchema: JSONSchema7 | undefined): string | undefined {
+ if (isVoidSchema(resultSchema)) return undefined;
+ return method.result?.description ?? resultSchema?.description;
+}
+
+function rpcParamsDescription(method: RpcMethod, effectiveParams: JSONSchema7 | undefined): string | undefined {
+ return method.params?.description ?? effectiveParams?.description;
+}
+
+function fallbackParameterDescription(name: string): string {
+ return name === "request" ? "The request parameters." : `The ${name} parameter.`;
+}
+
+function pushRpcMethodXmlDocs(
+ lines: string[],
+ method: RpcMethod,
+ indent: string,
+ parameterDescriptions: Array<{ name: string; description?: string; escapeDescription?: boolean }>,
+ resultSchema: JSONSchema7 | undefined,
+ summaryFallback?: string
+): void {
+ lines.push(...xmlDocComment(method.description ?? summaryFallback ?? `Calls "${method.rpcMethod}".`, indent));
+ for (const parameter of parameterDescriptions) {
+ lines.push(
+ ...xmlDocNamedElement(
+ "param",
+ parameter.name,
+ parameter.description ?? fallbackParameterDescription(parameter.name),
+ indent,
+ parameter.escapeDescription
+ )
+ );
+ }
+ lines.push(...xmlDocElement("returns", rpcResultDescription(method, resultSchema), indent));
+}
+
+const CANCELLATION_TOKEN_DESCRIPTION =
+ 'The to monitor for cancellation requests. The default is .';
+
/** Like xmlDocComment but skips XML escaping — use only for codegen-controlled strings that already contain valid XML tags. */
function rawXmlDocSummary(text: string, indent: string): string[] {
const line = ensureTrailingPunctuation(text.trim());
@@ -1658,17 +1736,9 @@ function emitServerInstanceMethod(
if (reqClass) classes.push(reqClass);
}
- lines.push("");
- lines.push(`${indent}/// Calls "${method.rpcMethod}".`);
- if (method.stability === "experimental" && !groupExperimental) {
- pushExperimentalAttribute(lines, indent);
- }
- if (method.deprecated && !groupDeprecated) {
- pushObsoleteAttributes(lines, indent);
- }
-
const sigParams: string[] = [];
const bodyAssignments: string[] = [];
+ const parameterDescriptions: Array<{ name: string; description?: string; escapeDescription?: boolean }> = [];
for (const [pName, pSchema] of paramEntries) {
if (typeof pSchema !== "object") continue;
@@ -1679,11 +1749,25 @@ function emitServerInstanceMethod(
: schemaTypeToCSharp(jsonSchema, isReq, rpcKnownTypes);
sigParams.push(`${csType} ${pName}${isReq ? "" : " = null"}`);
bodyAssignments.push(`${toPascalCase(pName)} = ${pName}`);
+ parameterDescriptions.push({ name: pName, description: jsonSchema.description });
}
sigParams.push("CancellationToken cancellationToken = default");
+ parameterDescriptions.push({
+ name: "cancellationToken",
+ description: CANCELLATION_TOKEN_DESCRIPTION,
+ escapeDescription: false,
+ });
const taskType = !isVoidSchema(resultSchema) ? `Task<${resultClassName}>` : "Task";
const localRequestName = localRequestVariableName(paramEntries);
+ lines.push("");
+ pushRpcMethodXmlDocs(lines, method, indent, parameterDescriptions, resultSchema);
+ if (method.stability === "experimental" && !groupExperimental) {
+ pushExperimentalAttribute(lines, indent);
+ }
+ if (method.deprecated && !groupDeprecated) {
+ pushObsoleteAttributes(lines, indent);
+ }
lines.push(`${indent}${methodVisibility} async ${taskType} ${methodName}Async(${sigParams.join(", ")})`);
lines.push(`${indent}{`);
if (requestClassName && bodyAssignments.length > 0) {
@@ -1783,18 +1867,13 @@ function emitSessionMethod(key: string, method: RpcMethod, lines: string[], clas
}
}
- lines.push("", `${indent}/// Calls "${method.rpcMethod}".`);
- if (method.stability === "experimental" && !groupExperimental) {
- pushExperimentalAttribute(lines, indent);
- }
- if (method.deprecated && !groupDeprecated) {
- pushObsoleteAttributes(lines, indent);
- }
const sigParams: string[] = [];
const bodyAssignments = [`SessionId = _sessionId`];
+ const parameterDescriptions: Array<{ name: string; description?: string; escapeDescription?: boolean }> = [];
if (useRequestParameter) {
sigParams.push(`${requestClassName}? request = null`);
+ parameterDescriptions.push({ name: "request", description: rpcParamsDescription(method, effectiveParams) });
for (const [pName] of paramEntries) {
bodyAssignments.push(`${toPascalCase(pName)} = request?.${toPascalCase(pName)}`);
}
@@ -1805,12 +1884,26 @@ function emitSessionMethod(key: string, method: RpcMethod, lines: string[], clas
const csType = resolveRpcType(pSchema as JSONSchema7, isReq, requestClassName, toPascalCase(pName), classes);
sigParams.push(`${csType} ${pName}${isReq ? "" : " = null"}`);
bodyAssignments.push(`${toPascalCase(pName)} = ${pName}`);
+ parameterDescriptions.push({ name: pName, description: (pSchema as JSONSchema7).description });
}
}
sigParams.push("CancellationToken cancellationToken = default");
+ parameterDescriptions.push({
+ name: "cancellationToken",
+ description: CANCELLATION_TOKEN_DESCRIPTION,
+ escapeDescription: false,
+ });
const taskType = !isVoidSchema(resultSchema) ? `Task<${resultClassName}>` : "Task";
const localRequestName = localRequestVariableName(paramEntries, useRequestParameter);
+ lines.push("");
+ pushRpcMethodXmlDocs(lines, method, indent, parameterDescriptions, resultSchema);
+ if (method.stability === "experimental" && !groupExperimental) {
+ pushExperimentalAttribute(lines, indent);
+ }
+ if (method.deprecated && !groupDeprecated) {
+ pushObsoleteAttributes(lines, indent);
+ }
lines.push(`${indent}${methodVisibility} async ${taskType} ${methodName}Async(${sigParams.join(", ")})`);
lines.push(`${indent}{`, `${indent} var ${localRequestName} = new ${wireRequestClassName} { ${bodyAssignments.join(", ")} };`);
if (!isVoidSchema(resultSchema)) {
@@ -1920,7 +2013,17 @@ function emitClientSessionApiRegistration(clientSchema: Record,
const hasParams = !!effectiveParams?.properties && Object.keys(effectiveParams.properties).length > 0;
const resultSchema = getMethodResultSchema(method);
const taskType = resultTaskType(method);
- lines.push(` /// Handles "${method.rpcMethod}".`);
+ pushRpcMethodXmlDocs(
+ lines,
+ method,
+ " ",
+ [
+ ...(hasParams ? [{ name: "request", description: rpcParamsDescription(method, effectiveParams) }] : []),
+ { name: "cancellationToken", description: CANCELLATION_TOKEN_DESCRIPTION, escapeDescription: false },
+ ],
+ resultSchema,
+ `Handles "${method.rpcMethod}".`
+ );
if (method.stability === "experimental" && !groupExperimental) {
pushExperimentalAttribute(lines, " ");
}
diff --git a/scripts/codegen/go.ts b/scripts/codegen/go.ts
index 717e0a82d..59c148112 100644
--- a/scripts/codegen/go.ts
+++ b/scripts/codegen/go.ts
@@ -163,6 +163,47 @@ function pushGoExperimentalMethodComment(lines: string[], methodName: string, in
pushGoComment(lines, `Experimental: ${methodName} is an experimental API and may change or be removed in future versions.`, indent);
}
+function lowerFirst(value: string): string {
+ if (value.length === 0) return value;
+ return value.charAt(0).toLowerCase() + value.slice(1);
+}
+
+function goMethodDocSummary(methodName: string, method: RpcMethod, fallbackVerb = "calls"): string {
+ const description = method.description?.trim();
+ if (!description) return `${methodName} ${fallbackVerb} ${method.rpcMethod}.`;
+ if (description.startsWith(methodName)) return description;
+ return `${methodName} ${lowerFirst(description)}`;
+}
+
+function goRpcResultDescription(method: RpcMethod, resultSchema: JSONSchema7 | undefined): string | undefined {
+ if (isVoidSchema(resultSchema)) return undefined;
+ return method.result?.description ?? resultSchema?.description;
+}
+
+function goRpcParamsDescription(method: RpcMethod, effectiveParams: JSONSchema7 | undefined): string | undefined {
+ return method.params?.description ?? effectiveParams?.description;
+}
+
+function pushGoRpcMethodComment(
+ lines: string[],
+ methodName: string,
+ method: RpcMethod,
+ resultSchema: JSONSchema7 | undefined,
+ paramsDescription?: string,
+ indent = "",
+ fallbackVerb = "calls"
+): void {
+ const paragraphs = [goMethodDocSummary(methodName, method, fallbackVerb), `RPC method: ${method.rpcMethod}.`];
+ if (paramsDescription) {
+ paragraphs.push(`Parameters: ${paramsDescription}`);
+ }
+ const resultDescription = goRpcResultDescription(method, resultSchema);
+ if (resultDescription) {
+ paragraphs.push(`Returns: ${resultDescription}`);
+ }
+ pushGoComment(lines, paragraphs.join("\n\n"), indent);
+}
+
function goCommentLines(text: string, indent = "", wrap = true): string[] {
const prefix = `${indent}//`;
const lines: string[] = [];
@@ -3721,6 +3762,13 @@ function emitMethod(lines: string[], receiver: string, name: string, method: Rpc
const clientRef = isWrapper ? "a.common.client" : "a.client";
const sessionIDRef = isWrapper ? "a.common.sessionID" : "a.sessionID";
+ pushGoRpcMethodComment(
+ lines,
+ methodName,
+ method,
+ resultSchema,
+ hasParams ? goRpcParamsDescription(method, effectiveParams) : undefined
+ );
if (method.deprecated && !groupDeprecated) {
pushGoComment(lines, `Deprecated: ${methodName} is deprecated and will be removed in a future version.`);
}
@@ -3834,6 +3882,16 @@ function emitClientSessionApiRegistration(lines: string[], clientSchema: Record<
}
lines.push(`type ${interfaceName} interface {`);
for (const method of methods) {
+ const resultSchema = getMethodResultSchema(method);
+ pushGoRpcMethodComment(
+ lines,
+ clientHandlerMethodName(method.rpcMethod),
+ method,
+ resultSchema,
+ goRpcParamsDescription(method, getMethodParamsSchema(method)),
+ "\t",
+ "handles"
+ );
if (method.deprecated && !groupDeprecated) {
pushGoComment(lines, `Deprecated: ${clientHandlerMethodName(method.rpcMethod)} is deprecated and will be removed in a future version.`, "\t");
}
@@ -3841,7 +3899,6 @@ function emitClientSessionApiRegistration(lines: string[], clientSchema: Record<
pushGoExperimentalMethodComment(lines, clientHandlerMethodName(method.rpcMethod), "\t");
}
const paramsType = resolveType(goParamsTypeName(method));
- const resultSchema = getMethodResultSchema(method);
const nullableInner = resultSchema ? getNullableInner(resultSchema) : undefined;
const resultType = nullableInner
? resolveType(goNullableResultTypeName(method, nullableInner))
diff --git a/scripts/codegen/python.ts b/scripts/codegen/python.ts
index ebad322ff..29c68da0d 100644
--- a/scripts/codegen/python.ts
+++ b/scripts/codegen/python.ts
@@ -277,6 +277,48 @@ function pyDocstringLiteral(text: string): string {
return JSON.stringify(normalized);
}
+function rpcResultDescription(method: RpcMethod, resultSchema: JSONSchema7 | undefined): string | undefined {
+ if (isVoidSchema(resultSchema)) return undefined;
+ return method.result?.description ?? resultSchema?.description;
+}
+
+function rpcParamsDescription(method: RpcMethod, effectiveParams: JSONSchema7 | undefined): string | undefined {
+ return method.params?.description ?? effectiveParams?.description;
+}
+
+function pushPyRpcMethodDocstring(
+ lines: string[],
+ indent: string,
+ method: RpcMethod,
+ options: {
+ paramsName?: string;
+ paramsDescription?: string;
+ resultDescription?: string;
+ deprecated?: boolean;
+ experimental?: boolean;
+ internal?: boolean;
+ } = {}
+): void {
+ const sections: string[] = [method.description ?? `Calls ${method.rpcMethod}.`];
+ if (options.paramsName && options.paramsDescription) {
+ sections.push(`Args:\n ${options.paramsName}: ${options.paramsDescription}`);
+ }
+ if (options.resultDescription) {
+ sections.push(`Returns:\n ${options.resultDescription}`);
+ }
+ if (options.deprecated) {
+ sections.push(".. deprecated:: This API is deprecated and will be removed in a future version.");
+ }
+ if (options.experimental) {
+ sections.push(".. warning:: This API is experimental and may change or be removed in future versions.");
+ }
+ if (options.internal) {
+ sections.push(":meta private:\n\nInternal SDK API; not part of the public surface.");
+ }
+
+ lines.push(`${indent}${pyDocstringLiteral(sections.join("\n\n"))}`);
+}
+
function modernizePython(code: string): string {
// Replace Optional[X] with X | None (handles arbitrarily nested brackets)
code = replaceBalancedBrackets(code, "Optional", (inner) => `${inner} | None`);
@@ -2463,15 +2505,14 @@ function emitMethod(lines: string[], name: string, method: RpcMethod, isSession:
lines.push(sig);
- if (method.deprecated && !groupDeprecated) {
- lines.push(` """.. deprecated:: This API is deprecated and will be removed in a future version."""`);
- }
- if (method.stability === "experimental" && !groupExperimental) {
- lines.push(` """.. warning:: This API is experimental and may change or be removed in future versions."""`);
- }
- if (method.visibility === "internal") {
- lines.push(` """:meta private: Internal SDK API; not part of the public surface."""`);
- }
+ pushPyRpcMethodDocstring(lines, " ", method, {
+ paramsName: hasParams ? "params" : undefined,
+ paramsDescription: rpcParamsDescription(method, effectiveParams),
+ resultDescription: rpcResultDescription(method, resultSchema),
+ deprecated: method.deprecated && !groupDeprecated,
+ experimental: method.stability === "experimental" && !groupExperimental,
+ internal: method.visibility === "internal",
+ });
// Deserialize helper
const innerTypeName = hasNullableResult ? resolveType(pythonResultTypeName(method, nullableInner)) : resultType;
@@ -2606,12 +2647,13 @@ function emitClientSessionHandlerMethod(
resultType = "None";
}
lines.push(` async def ${toSnakeCase(name)}(self, params: ${paramsType}) -> ${resultType}:`);
- if (method.deprecated && !groupDeprecated) {
- lines.push(` """.. deprecated:: This API is deprecated and will be removed in a future version."""`);
- }
- if (method.stability === "experimental" && !groupExperimental) {
- lines.push(` """.. warning:: This API is experimental and may change or be removed in future versions."""`);
- }
+ pushPyRpcMethodDocstring(lines, " ", method, {
+ paramsName: "params",
+ paramsDescription: rpcParamsDescription(method, getMethodParamsSchema(method)),
+ resultDescription: rpcResultDescription(method, resultSchema),
+ deprecated: method.deprecated && !groupDeprecated,
+ experimental: method.stability === "experimental" && !groupExperimental,
+ });
lines.push(` pass`);
}
diff --git a/scripts/codegen/rust.ts b/scripts/codegen/rust.ts
index c540bbdaa..1a5847033 100644
--- a/scripts/codegen/rust.ts
+++ b/scripts/codegen/rust.ts
@@ -432,6 +432,58 @@ function pushRustExperimentalDocs(
lines.push(`${indent}///
`);
}
+function pushRustDoc(lines: string[], text: string | undefined, indent = ""): void {
+ if (!text) return;
+ for (const paragraph of text.trim().split(/\r?\n/)) {
+ if (paragraph.trim().length === 0) {
+ lines.push(`${indent}///`);
+ } else {
+ lines.push(`${indent}/// ${paragraph.trim()}`);
+ }
+ }
+}
+
+function rustRpcResultDescription(
+ method: RpcMethod,
+ resultSchema: JSONSchema7 | undefined,
+): string | undefined {
+ if (isVoidSchema(resultSchema)) return undefined;
+ return method.result?.description ?? resultSchema?.description;
+}
+
+function rustRpcParamsDescription(
+ method: RpcMethod,
+ resolvedParams: JSONSchema7 | undefined,
+): string | undefined {
+ return method.params?.description ?? resolvedParams?.description;
+}
+
+function rustRpcMethodDocs(
+ method: RpcMethod,
+ resultSchema: JSONSchema7 | undefined,
+ paramsDescription: string | undefined,
+ includeParams: boolean,
+): string[] {
+ const docs: string[] = [];
+ pushRustDoc(docs, method.description ?? `Calls \`${method.rpcMethod}\`.`, " ");
+ docs.push(" ///");
+ docs.push(` /// Wire method: \`${method.rpcMethod}\`.`);
+ if (includeParams && paramsDescription) {
+ docs.push(" ///");
+ docs.push(" /// # Parameters");
+ docs.push(" ///");
+ pushRustDoc(docs, `* \`params\` - ${paramsDescription}`, " ");
+ }
+ const resultDescription = rustRpcResultDescription(method, resultSchema);
+ if (resultDescription) {
+ docs.push(" ///");
+ docs.push(" /// # Returns");
+ docs.push(" ///");
+ pushRustDoc(docs, resultDescription, " ");
+ }
+ return docs;
+}
+
// ── Type resolution ─────────────────────────────────────────────────────────
function rustRefTypeName(ref: string, definitions?: DefinitionCollections): string {
@@ -1104,13 +1156,23 @@ function collectRpcMethods(
return methods;
}
-function rustParamsTypeName(method: RpcMethod, ctx: RustCodegenCtx): string {
- if (method.params?.$ref && parseExternalSchemaRef(method.params.$ref)) {
- recordExternalRustTypeRef(method.params.$ref, ctx);
- return rustRefTypeName(method.params.$ref);
+function rustParamsTypeName(
+ method: RpcMethod,
+ context: DefinitionCollections | RustCodegenCtx,
+): string {
+ const ctx = "externalTypeRefs" in context ? context : undefined;
+ const defCollections = ctx?.definitions ?? context;
+ const params = method.params as (JSONSchema7 & { $ref?: string }) | undefined;
+ if (typeof params?.$ref === "string") {
+ if (ctx) {
+ recordExternalRustTypeRef(params.$ref, ctx);
+ }
+ return rustRefTypeName(params.$ref, defCollections);
}
+
+ const resolved = params ? resolveSchema(params, defCollections) : undefined;
return getRpcSchemaTypeName(
- method.params,
+ resolved ?? params,
`${toPascalCase(method.rpcMethod)}Params`,
);
}
@@ -1126,6 +1188,66 @@ function rustResultTypeName(method: RpcMethod, ctx: RustCodegenCtx): string {
);
}
+function asGeneratedObjectSchema(
+ schema: JSONSchema7,
+ defCollections: DefinitionCollections,
+): JSONSchema7 | undefined {
+ const resolved = resolveObjectSchema(schema, defCollections);
+ if (!resolved || !isObjectSchema(resolved)) return undefined;
+
+ return {
+ ...resolved,
+ title: resolved.title ?? schema.title,
+ description: schema.description ?? resolved.description,
+ };
+}
+
+function getMethodParamsObjectSchema(
+ method: RpcMethod,
+ defCollections: DefinitionCollections,
+ isSession: boolean,
+): JSONSchema7 | undefined {
+ if (!method.params) return undefined;
+
+ const resolved = asGeneratedObjectSchema(method.params, defCollections);
+ if (!resolved) return undefined;
+
+ const properties = { ...(resolved.properties ?? {}) };
+ if (isSession) {
+ delete properties.sessionId;
+ }
+
+ if (Object.keys(properties).length === 0) return undefined;
+
+ const required = (resolved.required ?? [])
+ .filter((name) => !isSession || name !== "sessionId")
+ .filter((name) => Object.prototype.hasOwnProperty.call(properties, name));
+
+ const schema: JSONSchema7 = {
+ ...resolved,
+ properties,
+ description: method.params.description ?? resolved.description,
+ };
+
+ if (required.length > 0) {
+ schema.required = required;
+ } else {
+ delete schema.required;
+ }
+
+ return schema;
+}
+
+function isNullableParamsSchema(
+ params: JSONSchema7,
+ defCollections: DefinitionCollections,
+): boolean {
+ if (getNullableInner(params)) return true;
+
+ const resolved = resolveSchema(params, defCollections);
+ return !!resolved && !!getNullableInner(resolved);
+}
+
function generateApiTypesCode(apiSchema: ApiSchema): string {
const definitions = collectDefinitions(apiSchema as Record
);
const defCollections = collectDefinitionCollections(
@@ -1146,24 +1268,35 @@ function generateApiTypesCode(apiSchema: ApiSchema): string {
schema.description,
isSchemaExperimental(schema),
);
+ } else if (asGeneratedObjectSchema(schema, defCollections)) {
+ emitRustStruct(
+ name,
+ asGeneratedObjectSchema(schema, defCollections)!,
+ ctx,
+ schema.description,
+ );
} else if (getUnionVariants(schema)) {
tryEmitRustUnion(schema, name, "", ctx);
- } else if (isObjectSchema(schema)) {
- emitRustStruct(name, schema, ctx, schema.description);
}
}
// Collect all RPC methods and generate request/response types
- const allMethods: RpcMethod[] = [];
- for (const group of [
- apiSchema.server,
- apiSchema.session,
- apiSchema.clientSession,
+ const methodEntries: { method: RpcMethod; isSession: boolean }[] = [];
+ for (const { group, isSession } of [
+ { group: apiSchema.server, isSession: false },
+ { group: apiSchema.session, isSession: true },
+ { group: apiSchema.clientSession, isSession: false },
]) {
if (group) {
- allMethods.push(...collectRpcMethods(group as Record));
+ methodEntries.push(
+ ...collectRpcMethods(group as Record).map((method) => ({
+ method,
+ isSession,
+ })),
+ );
}
}
+ const allMethods = methodEntries.map(({ method }) => method);
// RPC method name constants
const methodConstLines: string[] = [];
@@ -1180,14 +1313,24 @@ function generateApiTypesCode(apiSchema: ApiSchema): string {
methodConstLines.push("}");
// Generate param/result types for each method
- for (const method of allMethods) {
- if (
- method.params &&
- isObjectSchema(method.params) &&
- !isVoidSchema(method.params)
- ) {
+ for (const { method, isSession } of methodEntries) {
+ const paramsSchema = getMethodParamsObjectSchema(
+ method,
+ defCollections,
+ isSession,
+ );
+ const sessionWireParamsSchema = isSession && !paramsSchema && method.params
+ ? asGeneratedObjectSchema(method.params, defCollections)
+ : undefined;
+ const generatedParamsSchema = paramsSchema ?? sessionWireParamsSchema;
+ if (generatedParamsSchema) {
const paramsName = rustParamsTypeName(method, ctx);
- emitRustStruct(paramsName, method.params, ctx, method.params.description);
+ emitRustStruct(
+ paramsName,
+ generatedParamsSchema,
+ ctx,
+ generatedParamsSchema.description,
+ );
}
if (method.result && !isVoidSchema(method.result)) {
const resultName = rustResultTypeName(method, ctx);
@@ -1319,29 +1462,22 @@ function getMethodParamsInfo(
isSession: boolean,
): { hasParams: boolean; optional: boolean; typeName: string | null } {
if (!method.params) return { hasParams: false, optional: false, typeName: null };
- const inline = method.params as JSONSchema7 & { $ref?: string };
- const resolved = resolveObjectSchema(inline, defCollections) ??
- resolveSchema(inline, defCollections);
- if (!resolved) return { hasParams: false, optional: false, typeName: null };
-
- let typeName: string | null = null;
- if (typeof inline.$ref === "string") {
- typeName = refTypeName(inline.$ref, defCollections);
- } else if (typeof resolved.title === "string") {
- typeName = resolved.title;
- } else if (typeof inline.title === "string") {
- typeName = inline.title;
- }
-
- const allProps = Object.keys(resolved.properties || {});
- const props = isSession
- ? allProps.filter((p) => p !== "sessionId")
- : allProps;
+ const paramsSchema = getMethodParamsObjectSchema(
+ method,
+ defCollections,
+ isSession,
+ );
+ if (!paramsSchema) return { hasParams: false, optional: false, typeName: null };
+
+ const typeName = rustParamsTypeName(method, defCollections);
+
+ const props = Object.keys(paramsSchema.properties || {});
if (props.length === 0) return { hasParams: false, optional: false, typeName: null };
if (!typeName) return { hasParams: false, optional: false, typeName: null };
- const required = new Set(resolved.required || []);
+ const required = new Set(paramsSchema.required || []);
const hasRequiredParams = props.some((p) => required.has(p));
- const optional = !!getNullableInner(inline) && !hasRequiredParams;
+ const optional = isNullableParamsSchema(method.params, defCollections) &&
+ !hasRequiredParams;
return { hasParams: true, optional, typeName };
}
@@ -1489,52 +1625,67 @@ function emitNamespaceMethod(
const paramsInfo = getMethodParamsInfo(method, defCollections, isSession);
const hasParams = paramsInfo.hasParams;
const paramsTypeName = paramsInfo.typeName;
+ const resolvedParams = method.params
+ ? (resolveObjectSchema(method.params, defCollections) ??
+ resolveSchema(method.params, defCollections) ??
+ method.params)
+ : undefined;
const resultTypeName = getResultTypeName(method, defCollections);
+ const resultSchema = method.result
+ ? (resolveSchema(method.result, defCollections) ?? method.result)
+ : undefined;
const returnType = resultTypeName ? resultTypeName : "()";
const resultIsVoid = resultTypeName === null;
- const docs: string[] = [];
- docs.push(` /// Wire method: \`${wireMethod}\`.`);
- if (method.deprecated) docs.push(...rustDeprecatedAttributes(" "));
- const stability = method.stability;
- if (stability === "experimental") {
- docs.push(` ///`);
- docs.push(
- ` /// `,
- );
- docs.push(
- ` ///`,
- );
- docs.push(
- ` /// **Experimental.** This API is part of an experimental wire-protocol surface`,
- );
- docs.push(
- ` /// and may change or be removed in future SDK or CLI releases. Pin both the`,
- );
- docs.push(
- ` /// SDK and CLI versions if your code depends on it.`,
- );
- docs.push(
- ` ///`,
- );
- docs.push(
- ` ///
`,
+ const buildDocs = (includeParams: boolean): string[] => {
+ const docs = rustRpcMethodDocs(
+ method,
+ resultSchema,
+ rustRpcParamsDescription(method, resolvedParams),
+ includeParams,
);
- } else if (stability && stability !== "stable") {
- docs.push(` /// Stability: \`${stability}\`.`);
- }
+ if (method.deprecated) docs.push(...rustDeprecatedAttributes(" "));
+ const stability = method.stability;
+ if (stability === "experimental") {
+ docs.push(` ///`);
+ docs.push(
+ ` /// `,
+ );
+ docs.push(
+ ` ///`,
+ );
+ docs.push(
+ ` /// **Experimental.** This API is part of an experimental wire-protocol surface`,
+ );
+ docs.push(
+ ` /// and may change or be removed in future SDK or CLI releases. Pin both the`,
+ );
+ docs.push(
+ ` /// SDK and CLI versions if your code depends on it.`,
+ );
+ docs.push(
+ ` ///`,
+ );
+ docs.push(
+ ` ///
`,
+ );
+ } else if (stability && stability !== "stable") {
+ docs.push(` /// Stability: \`${stability}\`.`);
+ }
+ return docs;
+ };
const paramArg = hasParams ? `, params: ${paramsTypeName}` : "";
- out.push(...docs);
if (hasParams && paramsInfo.optional) {
+ out.push(...buildDocs(false));
out.push(
` pub async fn ${fnName}(&self) -> Result<${returnType}, Error> {`,
);
pushNamespaceMethodBody(out, constName, isSession, false, resultIsVoid);
out.push("");
- out.push(...docs);
+ out.push(...buildDocs(true));
out.push(
` pub async fn ${fnName}_with_params(&self, params: ${paramsTypeName}) -> Result<${returnType}, Error> {`,
);
@@ -1543,6 +1694,7 @@ function emitNamespaceMethod(
return;
}
+ out.push(...buildDocs(hasParams));
out.push(
` pub async fn ${fnName}(&self${paramArg}) -> Result<${returnType}, Error> {`,
);
diff --git a/scripts/codegen/typescript.ts b/scripts/codegen/typescript.ts
index cd0a0b07a..819d8adf9 100644
--- a/scripts/codegen/typescript.ts
+++ b/scripts/codegen/typescript.ts
@@ -46,6 +46,66 @@ function tsExperimentalJSDoc(indent = ""): string {
return `${indent}${TS_EXPERIMENTAL_JSDOC}`;
}
+function sanitizeJsDocText(text: string): string {
+ return text.trim().replace(/\*\//g, "* /");
+}
+
+function pushTsJsDoc(lines: string[], indent: string, entries: string[]): void {
+ const cleaned = entries.map(sanitizeJsDocText).filter((entry) => entry.length > 0);
+ if (cleaned.length === 0) return;
+
+ lines.push(`${indent}/**`);
+ for (const [index, entry] of cleaned.entries()) {
+ if (index > 0) {
+ lines.push(`${indent} *`);
+ }
+ for (const line of entry.split(/\r?\n/)) {
+ lines.push(`${indent} * ${line}`);
+ }
+ }
+ lines.push(`${indent} */`);
+}
+
+function rpcResultDescription(method: RpcMethod): string | undefined {
+ const resultSchema = getMethodResultSchema(method);
+ if (isVoidSchema(resultSchema)) return undefined;
+ return method.result?.description ?? resultSchema?.description;
+}
+
+function rpcParamsDescription(method: RpcMethod, effectiveParams: JSONSchema7 | undefined): string | undefined {
+ return method.params?.description ?? effectiveParams?.description;
+}
+
+function pushTsRpcMethodJsDoc(
+ lines: string[],
+ indent: string,
+ method: RpcMethod,
+ options: {
+ summaryFallback?: string;
+ paramsName?: string;
+ paramsDescription?: string;
+ includeDeprecated?: boolean;
+ includeExperimental?: boolean;
+ } = {}
+): void {
+ const entries: string[] = [];
+ entries.push(method.description ?? options.summaryFallback ?? `Calls \`${method.rpcMethod}\`.`);
+ if (options.paramsName && options.paramsDescription) {
+ entries.push(`@param ${options.paramsName} ${options.paramsDescription}`);
+ }
+ const resultDescription = rpcResultDescription(method);
+ if (resultDescription) {
+ entries.push(`@returns ${resultDescription}`);
+ }
+ if (options.includeDeprecated) {
+ entries.push("@deprecated");
+ }
+ if (options.includeExperimental) {
+ entries.push("@experimental");
+ }
+ pushTsJsDoc(lines, indent, entries);
+}
+
function toPascalCase(s: string): string {
return s.charAt(0).toUpperCase() + s.slice(1);
}
@@ -635,12 +695,12 @@ function emitGroup(
}
}
- if ((value as RpcMethod).deprecated && !parentDeprecated) {
- lines.push(`${indent}/** @deprecated */`);
- }
- if ((value as RpcMethod).stability === "experimental" && !parentExperimental) {
- lines.push(tsExperimentalJSDoc(indent));
- }
+ pushTsRpcMethodJsDoc(lines, indent, value, {
+ paramsName: sigParams.length > 0 ? "params" : undefined,
+ paramsDescription: rpcParamsDescription(value, effectiveParams),
+ includeDeprecated: (value as RpcMethod).deprecated && !parentDeprecated,
+ includeExperimental: (value as RpcMethod).stability === "experimental" && !parentExperimental,
+ });
lines.push(`${indent}${key}: async (${sigParams.join(", ")}): Promise<${resultType}> =>`);
lines.push(`${indent} connection.sendRequest("${rpcMethod}", ${bodyArg}),`);
} else if (typeof value === "object" && value !== null) {
@@ -711,6 +771,7 @@ function emitClientSessionApiRegistration(clientSchema: Record)
for (const [groupName, methods] of groups) {
const interfaceName = toPascalCase(groupName) + "Handler";
const groupDeprecated = isNodeFullyDeprecated(clientSchema[groupName] as Record);
+ const groupExperimental = isNodeFullyExperimental(clientSchema[groupName] as Record);
if (groupDeprecated) {
lines.push(`/** @deprecated Handler for \`${groupName}\` client session API methods. */`);
} else {
@@ -723,9 +784,13 @@ function emitClientSessionApiRegistration(clientSchema: Record)
const pType = hasParams ? paramsTypeName(method) : "";
const rType = tsResultType(method);
- if (method.deprecated && !groupDeprecated) {
- lines.push(` /** @deprecated */`);
- }
+ pushTsRpcMethodJsDoc(lines, " ", method, {
+ summaryFallback: `Handles \`${method.rpcMethod}\`.`,
+ paramsName: hasParams ? "params" : undefined,
+ paramsDescription: rpcParamsDescription(method, getMethodParamsSchema(method)),
+ includeDeprecated: method.deprecated && !groupDeprecated,
+ includeExperimental: method.stability === "experimental" && !groupExperimental,
+ });
if (hasParams) {
lines.push(` ${name}(params: ${pType}): Promise<${rType}>;`);
} else {
diff --git a/scripts/codegen/utils.ts b/scripts/codegen/utils.ts
index f38f005fb..6f06e8670 100644
--- a/scripts/codegen/utils.ts
+++ b/scripts/codegen/utils.ts
@@ -251,6 +251,7 @@ export async function writeGeneratedFile(relativePath: string, content: string):
export interface RpcMethod {
rpcMethod: string;
+ description?: string;
params: JSONSchema7 | null;
result: JSONSchema7 | null;
stability?: string;