mirror of
https://github.com/MoonshotAI/kimi-code.git
synced 2026-08-03 05:24:54 +00:00
* feat(agent-core-v2): add generated config section manifest - add scripts/gen-config-manifest.mts: drains the live registerConfigSection / registerConfigOverlay contributions and renders docs/config-manifest.toml in the on-disk config.toml shape (owner, scope, registered defaults, env bindings, schema fields) - add a gen:config-manifest package script (--check mode included) and a freshness test that rebuilds the manifest and compares byte-for-byte - point the agent-core-dev config skill and the package AGENTS.md at the generated manifest instead of the stale hand-maintained ownership map * feat(agent-core-v2): add generated wire-protocol manifest - add scripts/gen-wire-manifest.mts to generate docs/wire-manifest.d.ts from defineOp registrations (payload interfaces, persist policy, toEvent, cross-reducers) plus a WirePayloadMap - extract shared JSON Schema helpers from gen-config-manifest.mts into scripts/lib/jsonSchema.mts - add gen:wire-manifest script and wireManifest.test.ts freshness check - document the manifest in packages/agent-core-v2/AGENTS.md * fix(agent-core-v2): keep array-of-tables manifest sections fully commented A bare `[hooks]` header parses as a plain table, which array sections reject on load; emit only the commented `[[hooks]]` shape so the manifest matches the on-disk config.toml shape it documents. Addresses a Codex review comment on PR #2086.
99 lines
3.3 KiB
TypeScript
99 lines
3.3 KiB
TypeScript
/**
|
|
* Shared JSON-schema helpers for the manifest generators
|
|
* (`gen-config-manifest.mts`, `gen-wire-manifest.mts`).
|
|
*
|
|
* Both generators drain runtime registries that carry zod schemas and render
|
|
* field/type sketches from their JSON Schema projection.
|
|
*/
|
|
|
|
import { z } from 'zod';
|
|
|
|
export function isRecord(value: unknown): value is Record<string, unknown> {
|
|
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
}
|
|
|
|
export function truncate(text: string, max = 100): string {
|
|
return text.length > max ? `${text.slice(0, max - 1)}…` : text;
|
|
}
|
|
|
|
/** Property access shape of a JSON Schema node (avoids index-signature access). */
|
|
export interface JsonSchema {
|
|
readonly $ref?: unknown;
|
|
readonly $defs?: unknown;
|
|
readonly const?: unknown;
|
|
readonly enum?: unknown;
|
|
readonly anyOf?: unknown;
|
|
readonly oneOf?: unknown;
|
|
readonly type?: unknown;
|
|
readonly items?: unknown;
|
|
readonly properties?: unknown;
|
|
readonly required?: unknown;
|
|
readonly additionalProperties?: unknown;
|
|
readonly default?: unknown;
|
|
}
|
|
|
|
export function asJsonSchema(value: unknown): JsonSchema | undefined {
|
|
return isRecord(value) ? (value as JsonSchema) : undefined;
|
|
}
|
|
|
|
/** Resolve a `#/$defs/<name>` reference against the root schema. */
|
|
export function resolveRef(schema: unknown, root: JsonSchema): unknown {
|
|
const s = asJsonSchema(schema);
|
|
if (typeof s?.$ref === 'string' && s.$ref.startsWith('#/$defs/')) {
|
|
const defs = asJsonSchema(root.$defs);
|
|
const name = s.$ref.slice('#/$defs/'.length);
|
|
if (defs !== undefined && isRecord(defs) && name in defs) {
|
|
return (defs as Record<string, unknown>)[name];
|
|
}
|
|
}
|
|
return schema;
|
|
}
|
|
|
|
/** One-line type description of a JSON Schema node (`"a" | "b"`, `Foo[]`, …). */
|
|
export function describeType(
|
|
schema: unknown,
|
|
quoteString: (raw: string) => string = (s) => JSON.stringify(s),
|
|
): string {
|
|
const s = asJsonSchema(schema);
|
|
if (s === undefined) return 'any';
|
|
if (s.$ref !== undefined) {
|
|
return typeof s.$ref === 'string' ? (s.$ref.split('/').pop() ?? 'any') : 'any';
|
|
}
|
|
if (s.const !== undefined) {
|
|
return truncate(
|
|
typeof s.const === 'string' ? quoteString(s.const) : JSON.stringify(s.const),
|
|
40,
|
|
);
|
|
}
|
|
if (Array.isArray(s.enum)) {
|
|
return s.enum
|
|
.map((v) => (typeof v === 'string' ? quoteString(v) : JSON.stringify(v)))
|
|
.join(' | ');
|
|
}
|
|
for (const combiner of ['anyOf', 'oneOf'] as const) {
|
|
const subs = s[combiner];
|
|
if (Array.isArray(subs)) return subs.map((sub) => describeType(sub, quoteString)).join(' | ');
|
|
}
|
|
if (s.type === 'array') return `${describeType(s.items, quoteString)}[]`;
|
|
if (s.type === 'object') {
|
|
// Named sub-tables (zod objects emit `additionalProperties: false`) are
|
|
// rendered by the caller; only a schema-valued additionalProperties marks
|
|
// a true record.
|
|
if (isRecord(s.properties)) return 'object';
|
|
if (isRecord(s.additionalProperties)) {
|
|
return `record<string, ${describeType(s.additionalProperties, quoteString)}>`;
|
|
}
|
|
return 'object';
|
|
}
|
|
if (typeof s.type === 'string') return s.type;
|
|
return 'any';
|
|
}
|
|
|
|
/** Project a zod schema to JSON Schema; `undefined` when it uses transforms. */
|
|
export function toJsonSchema(schema: unknown): JsonSchema | undefined {
|
|
try {
|
|
return z.toJSONSchema(schema as never) as JsonSchema;
|
|
} catch {
|
|
return undefined;
|
|
}
|
|
}
|