





















@@ -400,35 +400,68 @@ function resolveJsonPointerPath(value: unknown, segments: string[]): unknown {
400400return current;
401401}
402402403-function tryResolveLocalRef(ref: string, defs: SchemaDefs | undefined): unknown {
404-if (!defs) {
403+function resolveLocalJsonPointer(rootDocument: unknown, ref: string): unknown {
404+if (!ref.startsWith("#/")) {
405405return undefined;
406406}
407+return resolveJsonPointerPath(rootDocument, ref.slice(2).split("/"));
408+}
409+410+const SCHEMA_MAP_KEYS = new Set([
411+"$defs",
412+"definitions",
413+"dependentSchemas",
414+"patternProperties",
415+"properties",
416+]);
417+418+const SCHEMA_OBJECT_KEYS = new Set([
419+"additionalProperties",
420+"contains",
421+"else",
422+"if",
423+"items",
424+"not",
425+"propertyNames",
426+"then",
427+]);
428+429+const SCHEMA_ARRAY_KEYS = new Set(["allOf", "anyOf", "items", "oneOf", "prefixItems"]);
430+431+const SCHEMA_LITERAL_KEYS = new Set(["const", "default", "enum", "examples"]);
432+433+function tryResolveLocalRef(
434+ref: string,
435+defs: SchemaDefs | undefined,
436+rootDocument: unknown,
437+): unknown {
407438const match = ref.match(/^#\/(\$defs|definitions)\/([^/]+)(?:\/(.*))?$/);
408-if (!match) {
409-return undefined;
410-}
411-const namespace = match[1] === "$defs" ? defs.$defs : defs.definitions;
412-const name = decodeJsonPointerSegment(match[2] ?? "");
413-const resolved = name ? namespace.get(name) : undefined;
414-if (resolved === undefined) {
415-return undefined;
439+if (match && defs) {
440+const namespace = match[1] === "$defs" ? defs.$defs : defs.definitions;
441+ const name = decodeJsonPointerSegment(match[2] ?? "");
442+ const resolved = name ? namespace.get(name) : undefined;
443+ if (resolved !== undefined) {
444+ const remainingPath = match[3] ? match[3].split("/") : [];
445+ return resolveJsonPointerPath(resolved, remainingPath);
446+}
416447}
417-const remainingPath = match[3] ? match[3].split("/") : [];
418-return resolveJsonPointerPath(resolved, remainingPath);
448+return resolveLocalJsonPointer(rootDocument, ref);
419449}
420450421451function inlineLocalSchemaRefsWithDefs(
422452schema: unknown,
423453defs: SchemaDefs | undefined,
424454refStack: Set<string> | undefined,
425455state: { unresolvedLocalRefs: boolean },
456+rootDocument: unknown,
426457): unknown {
427458if (!schema || typeof schema !== "object") {
428459return schema;
429460}
430461if (Array.isArray(schema)) {
431-return schema.map((entry) => inlineLocalSchemaRefsWithDefs(entry, defs, refStack, state));
462+return schema.map((entry) =>
463+inlineLocalSchemaRefsWithDefs(entry, defs, refStack, state, rootDocument),
464+);
432465}
433466434467const obj = schema as Record<string, unknown>;
@@ -439,7 +472,7 @@ function inlineLocalSchemaRefsWithDefs(
439472if (refStack?.has(refValue)) {
440473return {};
441474}
442-const resolved = tryResolveLocalRef(refValue, nextDefs);
475+const resolved = tryResolveLocalRef(refValue, nextDefs, rootDocument);
443476if (resolved === undefined) {
444477if (refValue.startsWith("#/")) {
445478state.unresolvedLocalRefs = true;
@@ -448,25 +481,65 @@ function inlineLocalSchemaRefsWithDefs(
448481}
449482const nextRefStack = refStack ? new Set(refStack) : new Set<string>();
450483nextRefStack.add(refValue);
451-const inlined = inlineLocalSchemaRefsWithDefs(resolved, nextDefs, nextRefStack, state);
484+const inlined = inlineLocalSchemaRefsWithDefs(
485+resolved,
486+nextDefs,
487+nextRefStack,
488+state,
489+rootDocument,
490+);
452491if (!inlined || typeof inlined !== "object" || Array.isArray(inlined)) {
453492return inlined;
454493}
455494const result: Record<string, unknown> = { ...(inlined as Record<string, unknown>) };
456495copySchemaMeta(obj, result);
496+if (obj.nullable === true) {
497+result.nullable = true;
498+}
457499return result;
458500}
459501460502const result: Record<string, unknown> = {};
461503for (const [key, value] of Object.entries(obj)) {
462-if (key === "$defs" || key === "definitions") {
504+if (key === "$defs" || key === "definitions" || key === "components") {
463505continue;
464506}
465-setOwnSchemaProperty(
466-result,
467-key,
468-inlineLocalSchemaRefsWithDefs(value, nextDefs, refStack, state),
469-);
507+if (SCHEMA_LITERAL_KEYS.has(key)) {
508+setOwnSchemaProperty(result, key, value);
509+continue;
510+}
511+if (SCHEMA_MAP_KEYS.has(key) && isSchemaRecord(value)) {
512+setOwnSchemaProperty(
513+result,
514+key,
515+Object.fromEntries(
516+Object.entries(value).map(([entryKey, entryValue]) => [
517+entryKey,
518+inlineLocalSchemaRefsWithDefs(entryValue, nextDefs, refStack, state, rootDocument),
519+]),
520+),
521+);
522+continue;
523+}
524+if (SCHEMA_OBJECT_KEYS.has(key) && isSchemaRecord(value)) {
525+setOwnSchemaProperty(
526+result,
527+key,
528+inlineLocalSchemaRefsWithDefs(value, nextDefs, refStack, state, rootDocument),
529+);
530+continue;
531+}
532+if (SCHEMA_ARRAY_KEYS.has(key) && Array.isArray(value)) {
533+setOwnSchemaProperty(
534+result,
535+key,
536+value.map((entry) =>
537+inlineLocalSchemaRefsWithDefs(entry, nextDefs, refStack, state, rootDocument),
538+),
539+);
540+continue;
541+}
542+setOwnSchemaProperty(result, key, value);
470543}
471544if (state.unresolvedLocalRefs) {
472545if ("$defs" in obj) {
@@ -475,6 +548,9 @@ function inlineLocalSchemaRefsWithDefs(
475548if ("definitions" in obj) {
476549result.definitions = obj.definitions;
477550}
551+if ("components" in obj) {
552+result.components = obj.components;
553+}
478554}
479555return result;
480556}
@@ -484,22 +560,164 @@ export function inlineLocalToolSchemaRefs(schema: unknown): TSchema {
484560return schema as TSchema;
485561}
486562const defs = extendSchemaDefs(undefined, schema as Record<string, unknown>);
487-return inlineLocalSchemaRefsWithDefs(schema, defs, undefined, {
488-unresolvedLocalRefs: false,
489-}) as TSchema;
563+return inlineLocalSchemaRefsWithDefs(
564+schema,
565+defs,
566+undefined,
567+{
568+unresolvedLocalRefs: false,
569+},
570+schema,
571+) as TSchema;
572+}
573+574+const OPENAPI_SCHEMA_ANNOTATION_KEYS = new Set([
575+"discriminator",
576+"externalDocs",
577+"readOnly",
578+"writeOnly",
579+"xml",
580+"example",
581+]);
582+583+function appendNullSchemaType(type: unknown): unknown {
584+if (type === "null") {
585+return type;
586+}
587+if (typeof type === "string") {
588+return [type, "null"];
589+}
590+if (Array.isArray(type)) {
591+return type.includes("null") ? type : [...type, "null"];
592+}
593+return type;
594+}
595+596+function isNullSchemaLike(schema: unknown): boolean {
597+if (!isSchemaRecord(schema)) {
598+return false;
599+}
600+if (schema.type === "null") {
601+return true;
602+}
603+if (Array.isArray(schema.type) && schema.type.includes("null")) {
604+return true;
605+}
606+if ("const" in schema && schema.const === null) {
607+return true;
608+}
609+return Array.isArray(schema.enum) && schema.enum.includes(null);
610+}
611+612+function hasOpenApiComposition(schema: Record<string, unknown>): boolean {
613+return ["allOf", "anyOf", "oneOf"].some((key) => Array.isArray(schema[key]));
614+}
615+616+function schemaCompositionAlreadyAllowsNull(schema: Record<string, unknown>): boolean {
617+return (
618+(Array.isArray(schema.anyOf) && schema.anyOf.some(isNullSchemaLike)) ||
619+(Array.isArray(schema.oneOf) && schema.oneOf.some(isNullSchemaLike))
620+);
621+}
622+623+function wrapNullableComposedSchema(schema: Record<string, unknown>): Record<string, unknown> {
624+if (schemaCompositionAlreadyAllowsNull(schema)) {
625+return schema;
626+}
627+const wrapped: Record<string, unknown> = {
628+anyOf: [schema, { type: "null" }],
629+};
630+copySchemaMeta(schema, wrapped);
631+return wrapped;
632+}
633+634+function normalizeOpenApiSchemaKeywords(schema: unknown): unknown {
635+if (Array.isArray(schema)) {
636+let changed = false;
637+const normalized = schema.map((entry) => {
638+const next = normalizeOpenApiSchemaKeywords(entry);
639+changed ||= next !== entry;
640+return next;
641+});
642+return changed ? normalized : schema;
643+}
644+if (!isSchemaRecord(schema)) {
645+return schema;
646+}
647+648+let changed = false;
649+const nullable = schema.nullable === true;
650+const normalized: Record<string, unknown> = {};
651+for (const [key, value] of Object.entries(schema)) {
652+if (key === "nullable" || OPENAPI_SCHEMA_ANNOTATION_KEYS.has(key)) {
653+changed = true;
654+continue;
655+}
656+if (SCHEMA_LITERAL_KEYS.has(key)) {
657+normalized[key] = value;
658+continue;
659+}
660+if (SCHEMA_MAP_KEYS.has(key) && isSchemaRecord(value)) {
661+let mapChanged = false;
662+const next = Object.fromEntries(
663+Object.entries(value).map(([entryKey, entryValue]) => {
664+const nextEntry = normalizeOpenApiSchemaKeywords(entryValue);
665+mapChanged ||= nextEntry !== entryValue;
666+return [entryKey, nextEntry];
667+}),
668+);
669+normalized[key] = mapChanged ? next : value;
670+changed ||= mapChanged;
671+continue;
672+}
673+if (key === "components") {
674+normalized[key] = value;
675+continue;
676+}
677+if (SCHEMA_OBJECT_KEYS.has(key) && isSchemaRecord(value)) {
678+const next = normalizeOpenApiSchemaKeywords(value);
679+normalized[key] = next;
680+changed ||= next !== value;
681+continue;
682+}
683+if (SCHEMA_ARRAY_KEYS.has(key) && Array.isArray(value)) {
684+const next = value.map(normalizeOpenApiSchemaKeywords);
685+normalized[key] = next;
686+changed ||= next.some((entry, index) => entry !== value[index]);
687+continue;
688+}
689+normalized[key] = value;
690+}
691+692+if (nullable) {
693+if (hasOpenApiComposition(normalized)) {
694+return wrapNullableComposedSchema(normalized);
695+}
696+if ("type" in normalized) {
697+const nextType = appendNullSchemaType(normalized.type);
698+if (nextType !== normalized.type) {
699+normalized.type = nextType;
700+}
701+}
702+if (Array.isArray(normalized.enum) && !normalized.enum.includes(null)) {
703+normalized.enum = [...normalized.enum, null];
704+}
705+}
706+707+return changed || nullable ? normalized : schema;
490708}
491709492710export function normalizeToolParameterSchema(
493711schema: unknown,
494712options?: { modelProvider?: string; modelId?: string; modelCompat?: ModelCompatConfig },
495713): TSchema {
496-const inlinedSchema = inlineLocalToolSchemaRefs(schema);
714+const inlinedSchema = normalizeOpenApiSchemaKeywords(inlineLocalToolSchemaRefs(schema));
497715const schemaRecord =
498716inlinedSchema && typeof inlinedSchema === "object"
499717 ? (inlinedSchema as Record<string, unknown>)
500718 : undefined;
501719if (!schemaRecord) {
502-return inlinedSchema;
720+return inlinedSchema as TSchema;
503721}
504722505723// Provider quirks:
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。