kimi-proxy 0.1.2 → 0.1.3

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.
@@ -16,7 +16,7 @@ export interface JsonObject {
16
16
  [key: string]: JsonValue | undefined;
17
17
  }
18
18
  export type JsonValue = JsonPrimitive | JsonObject | JsonArray;
19
- export declare function isJsonObject(value: JsonValue): value is JsonObject;
19
+ export declare function isJsonObject(value: JsonValue | undefined): value is JsonObject;
20
20
  export declare enum Operation {
21
21
  Chat = "chat",
22
22
  Messages = "messages",
@@ -2792,3 +2792,5 @@ export interface PipelineResult {
2792
2792
  }
2793
2793
  export type Json = JsonValue;
2794
2794
  export declare const isMessage: (value: unknown) => value is Message;
2795
+ export declare function getToolSchemas(request: Request): Record<string, z.ZodSchema>;
2796
+ export declare function findMatchingTool(request: Request, args: unknown): string | null;
package/dist/index.js CHANGED
@@ -1018,6 +1018,77 @@ var ResponseSchema = z3.object({
1018
1018
  synthetic: z3.boolean().optional()
1019
1019
  }).optional()
1020
1020
  });
1021
+ function jsonSchemaToZod(schema) {
1022
+ if (!schema || typeof schema !== "object" || Array.isArray(schema))
1023
+ return z3.any();
1024
+ const s = schema;
1025
+ switch (s.type) {
1026
+ case "object": {
1027
+ const shape = {};
1028
+ const properties = s.properties || {};
1029
+ const required = s.required || [];
1030
+ for (const key in properties) {
1031
+ let propSchema = jsonSchemaToZod(properties[key]);
1032
+ if (!required.includes(key)) {
1033
+ propSchema = propSchema.optional();
1034
+ }
1035
+ shape[key] = propSchema;
1036
+ }
1037
+ const zodObj = z3.object(shape);
1038
+ if (s.additionalProperties === false) {
1039
+ return zodObj.strict();
1040
+ } else {
1041
+ return zodObj.passthrough();
1042
+ }
1043
+ }
1044
+ case "array":
1045
+ return z3.array(jsonSchemaToZod(s.items || {}));
1046
+ case "string":
1047
+ if (Array.isArray(s.enum) && s.enum.length > 0 && s.enum.every((e) => typeof e === "string")) {
1048
+ return z3.enum(s.enum);
1049
+ }
1050
+ return z3.string();
1051
+ case "number":
1052
+ case "integer":
1053
+ return z3.number();
1054
+ case "boolean":
1055
+ return z3.boolean();
1056
+ case "null":
1057
+ return z3.null();
1058
+ default:
1059
+ return z3.any();
1060
+ }
1061
+ }
1062
+ var toolSchemaCache = new WeakMap;
1063
+ function getToolSchemas(request) {
1064
+ let schemas = toolSchemaCache.get(request);
1065
+ if (schemas)
1066
+ return schemas;
1067
+ schemas = {};
1068
+ for (const tool of request.tools ?? []) {
1069
+ schemas[tool.name] = jsonSchemaToZod(tool.parameters);
1070
+ }
1071
+ toolSchemaCache.set(request, schemas);
1072
+ return schemas;
1073
+ }
1074
+ function safeParseJson(str) {
1075
+ try {
1076
+ return JSON.parse(str);
1077
+ } catch {
1078
+ return str;
1079
+ }
1080
+ }
1081
+ function findMatchingTool(request, args) {
1082
+ const schemas = getToolSchemas(request);
1083
+ const matches = [];
1084
+ const parsedArgs = typeof args === "string" ? safeParseJson(args) : args;
1085
+ for (const [name, schema] of Object.entries(schemas)) {
1086
+ if (schema.safeParse(parsedArgs).success) {
1087
+ matches.push(name);
1088
+ }
1089
+ }
1090
+ return matches.length === 1 ? matches[0] : null;
1091
+ }
1021
1092
 
1022
1093
  // src/core/ensureToolCall.ts
1023
1094
  var ENSURE_TOOL_CALL_STATE_KEY = "__ensureToolCall";
@@ -2635,13 +2706,39 @@ function parseToolCalls(section) {
2635
2706
  }
2636
2707
  return toolCalls;
2637
2708
  }
2638
- function fixKimiResponse(response) {
2709
+ function repairToolNames(toolCalls, request) {
2710
+ const schemas = getToolSchemas(request);
2711
+ const toolNames = new Set(Object.keys(schemas));
2712
+ let repairedCount = 0;
2713
+ for (const call of toolCalls) {
2714
+ if (call.type !== "function" || !isJsonObject(call.function))
2715
+ continue;
2716
+ const fn = call.function;
2717
+ if (typeof fn.name === "number") {
2718
+ fn.name = String(fn.name);
2719
+ }
2720
+ const name = fn.name;
2721
+ if (typeof name !== "string")
2722
+ continue;
2723
+ if (toolNames.has(name))
2724
+ continue;
2725
+ const match = findMatchingTool(request, fn.arguments);
2726
+ if (match) {
2727
+ logger.debug(`[KimiFixer] Repaired tool name: ${name} -> ${match}`);
2728
+ fn.name = match;
2729
+ repairedCount++;
2730
+ }
2731
+ }
2732
+ return repairedCount;
2733
+ }
2734
+ function fixKimiResponse(response, request) {
2639
2735
  const metadata = {
2640
2736
  extractedToolCalls: 0,
2641
2737
  extractedFromReasoning: 0,
2642
2738
  extractedFromContent: 0,
2643
2739
  cleanedReasoningContent: false,
2644
- cleanedMessageContent: false
2740
+ cleanedMessageContent: false,
2741
+ repairedToolNames: 0
2645
2742
  };
2646
2743
  try {
2647
2744
  const choices = response?.choices;
@@ -2698,6 +2795,10 @@ ${thinkingContent}` : thinkingContent;
2698
2795
  }
2699
2796
  }
2700
2797
  if (aggregatedToolCalls.length) {
2798
+ const repaired = repairToolNames(aggregatedToolCalls, request);
2799
+ if (repaired > 0) {
2800
+ metadata.repairedToolNames = (metadata.repairedToolNames || 0) + repaired;
2801
+ }
2701
2802
  message.tool_calls = aggregatedToolCalls;
2702
2803
  choice.finish_reason = "tool_calls";
2703
2804
  } else if ("tool_calls" in message) {
@@ -2741,7 +2842,7 @@ var OpenAIToolCallSchema = z7.object({
2741
2842
  id: z7.string().optional(),
2742
2843
  type: z7.literal("function").optional(),
2743
2844
  function: z7.object({
2744
- name: z7.string(),
2845
+ name: z7.union([z7.string(), z7.number()]).transform(String),
2745
2846
  arguments: z7.union([z7.string(), z7.record(z7.unknown())]).optional()
2746
2847
  }).passthrough()
2747
2848
  }).passthrough();
@@ -2771,13 +2872,14 @@ function normalizeToolCalls(toolCalls) {
2771
2872
  const normalized = [];
2772
2873
  for (const call of toolCalls) {
2773
2874
  const fn = call?.function;
2774
- if (!fn || typeof fn.name !== "string")
2875
+ if (!fn || typeof fn.name !== "string" && typeof fn.name !== "number")
2775
2876
  continue;
2776
- const id = typeof call.id === "string" && call.id.length ? call.id : fn.name;
2877
+ const name = String(fn.name);
2878
+ const id = typeof call.id === "string" && call.id.length ? call.id : `${name}_call_${Math.random().toString(36).substring(2, 10)}`;
2777
2879
  normalized.push({
2778
2880
  id,
2779
2881
  type: "function",
2780
- name: fn.name,
2882
+ name,
2781
2883
  arguments: safeJsonString(fn.arguments)
2782
2884
  });
2783
2885
  }
@@ -2879,7 +2981,7 @@ function toOpenAIMessages(messages) {
2879
2981
  };
2880
2982
  });
2881
2983
  }
2882
- function normalizeOpenAIProviderResponse(payload) {
2984
+ function normalizeOpenAIProviderResponse(payload, request) {
2883
2985
  const parsed = OpenAIResponseSchema.safeParse(payload.body);
2884
2986
  if (!parsed.success) {
2885
2987
  const issue = parsed.error.issues[0]?.message ?? "Invalid provider payload";
@@ -2887,7 +2989,7 @@ function normalizeOpenAIProviderResponse(payload) {
2887
2989
  return { error: issue };
2888
2990
  }
2889
2991
  const cloned = structuredClone(parsed.data);
2890
- const { response, metadata } = fixKimiResponse(cloned);
2992
+ const { response, metadata } = fixKimiResponse(cloned, request);
2891
2993
  const normalized = OpenAIResponseSchema.safeParse(response);
2892
2994
  if (!normalized.success) {
2893
2995
  const issue = normalized.error.issues[0]?.message ?? "Provider payload invalid after normalization";
@@ -3002,7 +3104,7 @@ class OpenAIProviderAdapter {
3002
3104
  if (payload.status >= 400) {
3003
3105
  return toUlxErrorResponse(payload, request);
3004
3106
  }
3005
- const normalized = normalizeOpenAIProviderResponse(payload);
3107
+ const normalized = normalizeOpenAIProviderResponse(payload, request);
3006
3108
  if ("error" in normalized) {
3007
3109
  return {
3008
3110
  id: request.id,
@@ -3090,7 +3192,7 @@ class OpenRouterProviderAdapter {
3090
3192
  if (payload.status >= 400) {
3091
3193
  return toUlxErrorResponse(payload, request);
3092
3194
  }
3093
- const normalized = normalizeOpenAIProviderResponse(payload);
3195
+ const normalized = normalizeOpenAIProviderResponse(payload, request);
3094
3196
  if ("error" in normalized) {
3095
3197
  return {
3096
3198
  id: request.id,
@@ -3422,7 +3524,7 @@ class VertexProviderAdapter {
3422
3524
  return toUlxErrorResponse(payload, request);
3423
3525
  }
3424
3526
  if (request.model && MAAS_MODEL_PATTERN.test(request.model)) {
3425
- const normalized = normalizeOpenAIProviderResponse(payload);
3527
+ const normalized = normalizeOpenAIProviderResponse(payload, request);
3426
3528
  if ("error" in normalized) {
3427
3529
  return {
3428
3530
  id: request.id,
@@ -3977,7 +4079,7 @@ ${content}`
3977
4079
  logger.warn({ requestId: request.id }, "Detected prior assistant termination; requesting synthetic response");
3978
4080
  return true;
3979
4081
  }
3980
- if (toolCalls.length === 1 && this.checkTerminationHeuristic(message, toolCalls[0])) {
4082
+ if (toolCalls.length === 1 && this.checkTerminationHeuristic(message, toolCalls[0], request)) {
3981
4083
  requestSyntheticResponse(request.state);
3982
4084
  logger.warn({ requestId: request.id }, "Detected TodoWrite termination heuristic; requesting synthetic response");
3983
4085
  return true;
@@ -3985,7 +4087,10 @@ ${content}`
3985
4087
  }
3986
4088
  return false;
3987
4089
  }
3988
- checkTerminationHeuristic(message, toolCall) {
4090
+ checkTerminationHeuristic(message, toolCall, request) {
4091
+ if (!request.model.toLowerCase().includes("kimi")) {
4092
+ return false;
4093
+ }
3989
4094
  if (!hasCaseInsensitiveTerminationKeywords(message.content)) {
3990
4095
  return false;
3991
4096
  }
@@ -4014,7 +4119,7 @@ class EnsureToolCallResponseTransform {
4014
4119
  const response = context.response;
4015
4120
  if (!ensureState || !response)
4016
4121
  return;
4017
- if (this.checkTerminationHeuristic(response)) {
4122
+ if (this.checkTerminationHeuristic(response, context.request)) {
4018
4123
  ensureState.pendingReminder = false;
4019
4124
  logger.info({ requestId: context.request.id }, "EnsureToolCall satisfied by TodoWrite + keyword heuristics");
4020
4125
  return;
@@ -4088,7 +4193,10 @@ class EnsureToolCallResponseTransform {
4088
4193
  }
4089
4194
  return false;
4090
4195
  }
4091
- checkTerminationHeuristic(response) {
4196
+ checkTerminationHeuristic(response, request) {
4197
+ if (!request.model.toLowerCase().includes("kimi")) {
4198
+ return false;
4199
+ }
4092
4200
  const messageBlock = response.output.find((block) => block.type === "message");
4093
4201
  if (!messageBlock || messageBlock.type !== "message")
4094
4202
  return false;
@@ -5699,13 +5807,15 @@ async function handleRequest(req, reply, body, modelRegistry, pipeline, logStore
5699
5807
  method: req.method,
5700
5808
  url: req.url,
5701
5809
  statusCode: 400,
5810
+ provider: "schema_validation_failed",
5702
5811
  startedAt,
5703
5812
  finishedAt: Date.now(),
5704
5813
  requestBody: req.body,
5705
5814
  responseBody: errorBody,
5706
5815
  operation: options.operation,
5707
5816
  clientFormat: options.clientFormat,
5708
- profile: options.profile
5817
+ profile: options.profile,
5818
+ summary: summarizeError(error, "schema_validation")
5709
5819
  });
5710
5820
  reply.status(400).send(errorBody);
5711
5821
  return;
@@ -5730,13 +5840,15 @@ async function handleRequest(req, reply, body, modelRegistry, pipeline, logStore
5730
5840
  url: req.url,
5731
5841
  statusCode: 400,
5732
5842
  model,
5843
+ provider: "resolution_failed",
5733
5844
  startedAt,
5734
5845
  finishedAt: Date.now(),
5735
5846
  requestBody: parsedBody,
5736
5847
  responseBody: errorBody,
5737
5848
  operation: options.operation,
5738
5849
  clientFormat: options.clientFormat,
5739
- profile: options.profile
5850
+ profile: options.profile,
5851
+ summary: summarizeError(error, "model_resolution")
5740
5852
  });
5741
5853
  reply.status(400).send(errorBody);
5742
5854
  return;
@@ -5813,7 +5925,8 @@ async function handleRequest(req, reply, body, modelRegistry, pipeline, logStore
5813
5925
  requestBody: upstreamModel ? { ...parsedBody, model: upstreamModel } : parsedBody,
5814
5926
  responseBody: { error: errorDetails },
5815
5927
  providerRequestBody: null,
5816
- providerResponseBody: null
5928
+ providerResponseBody: null,
5929
+ summary: summarizeError(error, "pipeline_execution")
5817
5930
  });
5818
5931
  reply.status(500).send({ error: { message: "Internal proxy error" } });
5819
5932
  }
@@ -5902,6 +6015,13 @@ function summarize(response) {
5902
6015
  preview
5903
6016
  });
5904
6017
  }
6018
+ function summarizeError(error, errorType) {
6019
+ const errorMessage = error instanceof Error ? error.message : String(error);
6020
+ return JSON.stringify({
6021
+ error_type: errorType,
6022
+ error_message: errorMessage.slice(0, 500)
6023
+ });
6024
+ }
5905
6025
  function isPrematureCloseError(error) {
5906
6026
  if (!error || typeof error !== "object")
5907
6027
  return false;
@@ -5925,5 +6045,5 @@ async function bootstrap() {
5925
6045
  }
5926
6046
  bootstrap();
5927
6047
 
5928
- //# debugId=C3410C5B307F3A9164756E2164756E21
6048
+ //# debugId=49E2D016D6E3465664756E2164756E21
5929
6049
  //# sourceMappingURL=index.js.map