@oh-my-pi/pi-ai 16.3.5 → 16.3.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1014,6 +1014,137 @@ export function normalizeSchemaForMoonshot(value: unknown): unknown {
1014
1014
  });
1015
1015
  }
1016
1016
 
1017
+ // ---------------------------------------------------------------------------
1018
+ // Ollama — Go schema parser compatibility
1019
+ // ---------------------------------------------------------------------------
1020
+
1021
+ const OLLAMA_SCHEMA_ARRAY_KEYS = new Set(["anyOf", "oneOf", "allOf", "prefixItems"]);
1022
+ const OLLAMA_SCHEMA_MAP_KEYS = new Set([
1023
+ "properties",
1024
+ "patternProperties",
1025
+ "dependencies",
1026
+ "dependentSchemas",
1027
+ "$defs",
1028
+ "definitions",
1029
+ ]);
1030
+ const OLLAMA_SCHEMA_VALUE_KEYS = new Set([
1031
+ "items",
1032
+ "additionalItems",
1033
+ "contains",
1034
+ "contentSchema",
1035
+ "propertyNames",
1036
+ "if",
1037
+ "then",
1038
+ "else",
1039
+ "not",
1040
+ "additionalProperties",
1041
+ "unevaluatedItems",
1042
+ "unevaluatedProperties",
1043
+ ]);
1044
+
1045
+ /**
1046
+ * Widened stand-in for a `true` / `{}` subschema in an Ollama-bound tool.
1047
+ *
1048
+ * `toolWireSchema()` normalizes empty schemas to boolean `true` upstream so
1049
+ * grammar-constrained samplers (llama.cpp, etc.) don't treat `{}` as
1050
+ * "generate an empty object" (issue #1179). Ollama's Go tool parser can't
1051
+ * unmarshal a boolean into its object-shaped `Schema` struct, so this
1052
+ * sanitizer replaces every open subschema with an explicit union of every
1053
+ * primitive JSON type. Both invariants survive: the wire has no boolean
1054
+ * subschema (Go accepts it), and llama.cpp's grammar sees a real value
1055
+ * union rather than a closed empty object.
1056
+ */
1057
+ const OLLAMA_OPEN_SUBSCHEMA_WIDENING = Object.freeze({
1058
+ anyOf: [
1059
+ { type: "string" },
1060
+ { type: "number" },
1061
+ { type: "boolean" },
1062
+ { type: "object" },
1063
+ { type: "array" },
1064
+ { type: "null" },
1065
+ ],
1066
+ });
1067
+
1068
+ /**
1069
+ * Rewrites standard JSON Schema forms that Ollama's Go `/api/chat` tool parser
1070
+ * cannot unmarshal into its object-shaped `Schema` struct.
1071
+ */
1072
+ export function sanitizeSchemaForOllama(schema: JsonObject): JsonObject {
1073
+ const normalizeNode = (value: unknown): unknown => {
1074
+ if (value === true) return OLLAMA_OPEN_SUBSCHEMA_WIDENING;
1075
+ if (value === false) return { not: OLLAMA_OPEN_SUBSCHEMA_WIDENING };
1076
+ if (!isJsonObject(value)) {
1077
+ if (!Array.isArray(value)) return value;
1078
+ let changed = false;
1079
+ const output = value.map(item => {
1080
+ const next = normalizeNode(item);
1081
+ if (next !== item) changed = true;
1082
+ return next;
1083
+ });
1084
+ return changed ? output : value;
1085
+ }
1086
+
1087
+ let changed = false;
1088
+ const output: JsonObject = {};
1089
+ let typeAlternatives: JsonObject[] | undefined;
1090
+ for (const key in value) {
1091
+ if (!Object.hasOwn(value, key)) continue;
1092
+ const child = value[key];
1093
+ if ((key === "additionalProperties" || key === "unevaluatedProperties") && typeof child === "boolean") {
1094
+ changed = true;
1095
+ continue;
1096
+ }
1097
+ if (key === "type" && Array.isArray(child)) {
1098
+ const variants = child.filter((entry): entry is string => typeof entry === "string");
1099
+ const uniqueVariants = [...new Set(variants)];
1100
+ const nonNull = uniqueVariants.filter(entry => entry !== "null");
1101
+ if (nonNull.length <= 1) {
1102
+ output.type = nonNull[0] ?? uniqueVariants[0] ?? child[0];
1103
+ } else {
1104
+ typeAlternatives = uniqueVariants.map(entry => ({ type: entry }));
1105
+ }
1106
+ changed = true;
1107
+ continue;
1108
+ }
1109
+
1110
+ let next = child;
1111
+ if (OLLAMA_SCHEMA_MAP_KEYS.has(key) && isJsonObject(child)) {
1112
+ let mapChanged = false;
1113
+ const mapOutput: JsonObject = {};
1114
+ for (const childKey in child) {
1115
+ if (!Object.hasOwn(child, childKey)) continue;
1116
+ const mapChild = child[childKey];
1117
+ const normalizedChild = normalizeNode(mapChild);
1118
+ if (normalizedChild !== mapChild) mapChanged = true;
1119
+ mapOutput[childKey] = normalizedChild;
1120
+ }
1121
+ next = mapChanged ? mapOutput : child;
1122
+ } else if (OLLAMA_SCHEMA_ARRAY_KEYS.has(key) && Array.isArray(child)) {
1123
+ let arrayChanged = false;
1124
+ const arrayOutput = child.map(item => {
1125
+ const normalizedItem = normalizeNode(item);
1126
+ if (normalizedItem !== item) arrayChanged = true;
1127
+ return normalizedItem;
1128
+ });
1129
+ next = arrayChanged ? arrayOutput : child;
1130
+ } else if (OLLAMA_SCHEMA_VALUE_KEYS.has(key)) {
1131
+ next = normalizeNode(child);
1132
+ }
1133
+ if (next !== child) changed = true;
1134
+ output[key] = next;
1135
+ }
1136
+
1137
+ if (typeAlternatives) {
1138
+ const existingAllOf = output.allOf;
1139
+ const typeUnion = { anyOf: typeAlternatives };
1140
+ output.allOf = Array.isArray(existingAllOf) ? [typeUnion, ...existingAllOf] : [typeUnion];
1141
+ }
1142
+
1143
+ return changed ? output : value;
1144
+ };
1145
+ return normalizeNode(schema) as JsonObject;
1146
+ }
1147
+
1017
1148
  // ---------------------------------------------------------------------------
1018
1149
  // OpenAI Responses — schema-valued normalization
1019
1150
  // ---------------------------------------------------------------------------