@copilotkit/aimock 1.10.0 → 1.12.0

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.
Files changed (167) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/README.md +4 -2
  4. package/dist/a2a-types.d.cts.map +1 -1
  5. package/dist/a2a-types.d.ts.map +1 -1
  6. package/dist/agui-handler.cjs +340 -0
  7. package/dist/agui-handler.cjs.map +1 -0
  8. package/dist/agui-handler.d.cts +96 -0
  9. package/dist/agui-handler.d.cts.map +1 -0
  10. package/dist/agui-handler.d.ts +96 -0
  11. package/dist/agui-handler.d.ts.map +1 -0
  12. package/dist/agui-handler.js +326 -0
  13. package/dist/agui-handler.js.map +1 -0
  14. package/dist/agui-mock.cjs +190 -0
  15. package/dist/agui-mock.cjs.map +1 -0
  16. package/dist/agui-mock.d.cts +50 -0
  17. package/dist/agui-mock.d.cts.map +1 -0
  18. package/dist/agui-mock.d.ts +50 -0
  19. package/dist/agui-mock.d.ts.map +1 -0
  20. package/dist/agui-mock.js +188 -0
  21. package/dist/agui-mock.js.map +1 -0
  22. package/dist/agui-recorder.cjs +153 -0
  23. package/dist/agui-recorder.cjs.map +1 -0
  24. package/dist/agui-recorder.d.cts +19 -0
  25. package/dist/agui-recorder.d.cts.map +1 -0
  26. package/dist/agui-recorder.d.ts +19 -0
  27. package/dist/agui-recorder.d.ts.map +1 -0
  28. package/dist/agui-recorder.js +147 -0
  29. package/dist/agui-recorder.js.map +1 -0
  30. package/dist/agui-types.d.cts +231 -0
  31. package/dist/agui-types.d.cts.map +1 -0
  32. package/dist/agui-types.d.ts +231 -0
  33. package/dist/agui-types.d.ts.map +1 -0
  34. package/dist/bedrock-converse.cjs +2 -0
  35. package/dist/bedrock-converse.cjs.map +1 -1
  36. package/dist/bedrock-converse.d.cts.map +1 -1
  37. package/dist/bedrock-converse.d.ts.map +1 -1
  38. package/dist/bedrock-converse.js +2 -0
  39. package/dist/bedrock-converse.js.map +1 -1
  40. package/dist/bedrock.cjs +2 -0
  41. package/dist/bedrock.cjs.map +1 -1
  42. package/dist/bedrock.d.cts.map +1 -1
  43. package/dist/bedrock.d.ts.map +1 -1
  44. package/dist/bedrock.js +2 -0
  45. package/dist/bedrock.js.map +1 -1
  46. package/dist/cli.cjs +32 -1
  47. package/dist/cli.cjs.map +1 -1
  48. package/dist/cli.js +32 -1
  49. package/dist/cli.js.map +1 -1
  50. package/dist/cohere.cjs +1 -0
  51. package/dist/cohere.cjs.map +1 -1
  52. package/dist/cohere.js +1 -0
  53. package/dist/cohere.js.map +1 -1
  54. package/dist/config-loader.cjs +19 -0
  55. package/dist/config-loader.cjs.map +1 -1
  56. package/dist/config-loader.d.cts +16 -0
  57. package/dist/config-loader.d.cts.map +1 -1
  58. package/dist/config-loader.d.ts +16 -0
  59. package/dist/config-loader.d.ts.map +1 -1
  60. package/dist/config-loader.js +19 -0
  61. package/dist/config-loader.js.map +1 -1
  62. package/dist/embeddings.cjs +2 -1
  63. package/dist/embeddings.cjs.map +1 -1
  64. package/dist/embeddings.js +2 -1
  65. package/dist/embeddings.js.map +1 -1
  66. package/dist/gemini.cjs +1 -0
  67. package/dist/gemini.cjs.map +1 -1
  68. package/dist/gemini.js +1 -0
  69. package/dist/gemini.js.map +1 -1
  70. package/dist/helpers.cjs +16 -0
  71. package/dist/helpers.cjs.map +1 -1
  72. package/dist/helpers.d.cts +6 -2
  73. package/dist/helpers.d.cts.map +1 -1
  74. package/dist/helpers.d.ts +6 -2
  75. package/dist/helpers.d.ts.map +1 -1
  76. package/dist/helpers.js +13 -1
  77. package/dist/helpers.js.map +1 -1
  78. package/dist/images.cjs +166 -0
  79. package/dist/images.cjs.map +1 -0
  80. package/dist/images.d.cts +11 -0
  81. package/dist/images.d.cts.map +1 -0
  82. package/dist/images.d.ts +11 -0
  83. package/dist/images.d.ts.map +1 -0
  84. package/dist/images.js +166 -0
  85. package/dist/images.js.map +1 -0
  86. package/dist/index.cjs +32 -0
  87. package/dist/index.d.cts +11 -3
  88. package/dist/index.d.ts +11 -3
  89. package/dist/index.js +9 -2
  90. package/dist/llmock.cjs +37 -1
  91. package/dist/llmock.cjs.map +1 -1
  92. package/dist/llmock.d.cts +5 -1
  93. package/dist/llmock.d.cts.map +1 -1
  94. package/dist/llmock.d.ts +5 -1
  95. package/dist/llmock.d.ts.map +1 -1
  96. package/dist/llmock.js +37 -1
  97. package/dist/llmock.js.map +1 -1
  98. package/dist/messages.cjs +1 -0
  99. package/dist/messages.cjs.map +1 -1
  100. package/dist/messages.js +1 -0
  101. package/dist/messages.js.map +1 -1
  102. package/dist/ollama.cjs +2 -0
  103. package/dist/ollama.cjs.map +1 -1
  104. package/dist/ollama.d.cts.map +1 -1
  105. package/dist/ollama.d.ts.map +1 -1
  106. package/dist/ollama.js +2 -0
  107. package/dist/ollama.js.map +1 -1
  108. package/dist/recorder.cjs +50 -7
  109. package/dist/recorder.cjs.map +1 -1
  110. package/dist/recorder.js +50 -7
  111. package/dist/recorder.js.map +1 -1
  112. package/dist/responses.cjs +1 -0
  113. package/dist/responses.cjs.map +1 -1
  114. package/dist/responses.js +1 -0
  115. package/dist/responses.js.map +1 -1
  116. package/dist/router.cjs +8 -0
  117. package/dist/router.cjs.map +1 -1
  118. package/dist/router.d.cts.map +1 -1
  119. package/dist/router.d.ts.map +1 -1
  120. package/dist/router.js +9 -0
  121. package/dist/router.js.map +1 -1
  122. package/dist/server.cjs +80 -3
  123. package/dist/server.cjs.map +1 -1
  124. package/dist/server.d.cts +2 -0
  125. package/dist/server.d.cts.map +1 -1
  126. package/dist/server.d.ts +2 -0
  127. package/dist/server.d.ts.map +1 -1
  128. package/dist/server.js +80 -3
  129. package/dist/server.js.map +1 -1
  130. package/dist/speech.cjs +144 -0
  131. package/dist/speech.cjs.map +1 -0
  132. package/dist/speech.d.cts +11 -0
  133. package/dist/speech.d.cts.map +1 -0
  134. package/dist/speech.d.ts +11 -0
  135. package/dist/speech.d.ts.map +1 -0
  136. package/dist/speech.js +144 -0
  137. package/dist/speech.js.map +1 -0
  138. package/dist/suite.cjs +8 -0
  139. package/dist/suite.cjs.map +1 -1
  140. package/dist/suite.d.cts +4 -0
  141. package/dist/suite.d.cts.map +1 -1
  142. package/dist/suite.d.ts +4 -0
  143. package/dist/suite.d.ts.map +1 -1
  144. package/dist/suite.js +8 -0
  145. package/dist/suite.js.map +1 -1
  146. package/dist/transcription.cjs +134 -0
  147. package/dist/transcription.cjs.map +1 -0
  148. package/dist/transcription.d.cts +11 -0
  149. package/dist/transcription.d.cts.map +1 -0
  150. package/dist/transcription.d.ts +11 -0
  151. package/dist/transcription.d.ts.map +1 -0
  152. package/dist/transcription.js +134 -0
  153. package/dist/transcription.js.map +1 -0
  154. package/dist/types.d.cts +44 -2
  155. package/dist/types.d.cts.map +1 -1
  156. package/dist/types.d.ts +44 -2
  157. package/dist/types.d.ts.map +1 -1
  158. package/dist/vector-types.d.ts.map +1 -1
  159. package/dist/video.cjs +196 -0
  160. package/dist/video.cjs.map +1 -0
  161. package/dist/video.d.cts +14 -0
  162. package/dist/video.d.cts.map +1 -0
  163. package/dist/video.d.ts +14 -0
  164. package/dist/video.d.ts.map +1 -0
  165. package/dist/video.js +195 -0
  166. package/dist/video.js.map +1 -0
  167. package/package.json +2 -2
@@ -2,6 +2,7 @@ import { ChaosConfig, RecordConfig } from "./types.cjs";
2
2
  import { A2AAgentDefinition, A2AArtifact, A2APart, A2AStreamEvent } from "./a2a-types.cjs";
3
3
  import { LLMock } from "./llmock.cjs";
4
4
  import { MCPPromptDefinition, MCPToolDefinition } from "./mcp-types.cjs";
5
+ import { AGUIEvent } from "./agui-types.cjs";
5
6
  import { QueryResult } from "./vector-types.cjs";
6
7
 
7
8
  //#region src/config-loader.d.ts
@@ -53,6 +54,20 @@ interface A2AConfig {
53
54
  path?: string;
54
55
  agents?: A2AConfigAgent[];
55
56
  }
57
+ interface AGUIConfigFixture {
58
+ match: {
59
+ message?: string;
60
+ toolName?: string;
61
+ stateKey?: string;
62
+ };
63
+ text?: string;
64
+ events?: AGUIEvent[];
65
+ delayMs?: number;
66
+ }
67
+ interface AGUIConfig {
68
+ path?: string;
69
+ fixtures?: AGUIConfigFixture[];
70
+ }
56
71
  interface VectorConfigCollection {
57
72
  name: string;
58
73
  dimension: number;
@@ -75,6 +90,7 @@ interface AimockConfig {
75
90
  };
76
91
  mcp?: MCPConfig;
77
92
  a2a?: A2AConfig;
93
+ agui?: AGUIConfig;
78
94
  vector?: VectorConfig;
79
95
  services?: {
80
96
  search?: boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"config-loader.d.cts","names":[],"sources":["../src/config-loader.ts"],"sourcesContent":[],"mappings":";;;;;;;UAYiB,aAAA,SAAsB;;AAAvC;AAIiB,UAAA,iBAAA,CAAiB;EASjB,GAAA,EAAA,MAAA;EAAgB,IAAA,EAAA,MAAA;UAEnB,CAAA,EAAA,MAAA;aAF2B,CAAA,EAAA,MAAA;EAAmB,IAAA,CAAA,EAAA,MAAA;EAM3C,IAAA,CAAA,EAAA,MAAS;;AAGhB,UATO,eAAA,SAAwB,mBAS/B,CAAA;QACI,CAAA,EAAA;IACF,QAAA,EATE,KASF,CAAA;MAAe,IAAA,EAAA,MAAA;MAGV,OAAA,EAAA;QAAgB,IAAA,EAAA,MAAA;QAEvB,IAAA,EAAA,MAAA;MACI,CAAA;IACH,CAAA,CAAA;EAAc,CAAA;AAIzB;AAAgC,UAhBf,SAAA,CAgBe;MACnB,CAAA,EAAA,MAAA;YACH,CAAA,EAAA;IACS,IAAA,EAAA,MAAA;IAHqB,OAAA,EAAA,MAAA;EAAkB,CAAA;EAMzC,KAAA,CAAA,EAnBP,aAmBgB,EAEf;EAGM,SAAA,CAAA,EAvBH,iBAuByB,EAAA;EAAA,OAAA,CAAA,EAtB3B,eAsB2B,EAAA;;AAG3B,UAtBK,gBAAA,CAsBL;SAKK,EAAA,MAAA;EAAW,KAAA,CAAA,EAzBlB,OAyBkB,EAAA;EAGX,SAAA,CAAA,EA3BH,WA2Be,EAAA;EAKZ,MAAA,CAAA,EA/BN,cA+BkB,EAAA;EAAA,OAAA,CAAA,EAAA,MAAA;;AAIhB,UA/BI,cAAA,SAAuB,kBA+B3B,CAAA;UAEL,CAAA,EAhCK,gBAgCL,EAAA;OACA,CAAA,EAhCE,gBAgCF,EAAA;gBACG,CAAA,EAhCQ,gBAgCR,EAAA;;AAQK,UArCC,SAAA,CAqCS;EAKJ,IAAA,CAAA,EAAA,MAAA;EAAe,MAAA,CAAA,EAxC1B,cAwC0B,EAAA;;AAGhB,UAxCJ,sBAAA,CAwCI;MAAlB,EAAA,MAAA;EAAO,SAAA,EAAA,MAAA;YArCE;;;eAGG;;iBAEE;;UAGA,YAAA;;gBAED;;UAGC,YAAA;;;YAGL;aACC;;QAEL;QACA;WACG;;;;;;;;;;;iBAQK,UAAA,sBAAgC;iBAK1B,eAAA,SACZ;;;IAEP;UAAkB"}
1
+ {"version":3,"file":"config-loader.d.cts","names":[],"sources":["../src/config-loader.ts"],"sourcesContent":[],"mappings":";;;;;;;;UAciB,aAAA,SAAsB;;AAAvC;AAIiB,UAAA,iBAAA,CAAiB;EASjB,GAAA,EAAA,MAAA;EAAgB,IAAA,EAAA,MAAA;UAEnB,CAAA,EAAA,MAAA;aAF2B,CAAA,EAAA,MAAA;EAAmB,IAAA,CAAA,EAAA,MAAA;EAM3C,IAAA,CAAA,EAAA,MAAS;;AAGhB,UATO,eAAA,SAAwB,mBAS/B,CAAA;QACI,CAAA,EAAA;IACF,QAAA,EATE,KASF,CAAA;MAAe,IAAA,EAAA,MAAA;MAGV,OAAA,EAAA;QAAgB,IAAA,EAAA,MAAA;QAEvB,IAAA,EAAA,MAAA;MACI,CAAA;IACH,CAAA,CAAA;EAAc,CAAA;AAIzB;AAAgC,UAhBf,SAAA,CAgBe;MACnB,CAAA,EAAA,MAAA;YACH,CAAA,EAAA;IACS,IAAA,EAAA,MAAA;IAHqB,OAAA,EAAA,MAAA;EAAkB,CAAA;EAMzC,KAAA,CAAA,EAnBP,aAmBgB,EAEf;EAGM,SAAA,CAAA,EAvBH,iBAuBoB,EAGvB;EAIM,OAAA,CAAA,EA7BL,eA+BC,EAAA;AAGb;AAAuC,UA/BtB,gBAAA,CA+BsB;SAMxB,EAAA,MAAA;OAHH,CAAA,EAhCF,OAgCE,EAAA;WAKK,CAAA,EApCH,WAoCG,EAAA;EAAW,MAAA,CAAA,EAnCjB,cAmCiB,EAAA;EAGX,OAAA,CAAA,EAAA,MAAY;AAK7B;AAA6B,UAvCZ,cAAA,SAAuB,kBAuCX,CAAA;UAGjB,CAAA,EAzCC,gBAyCD,EAAA;OACC,CAAA,EAzCH,gBAyCG,EAAA;gBAEL,CAAA,EA1CW,gBA0CX,EAAA;;AAEC,UAzCQ,SAAA,CAyCR;MACE,CAAA,EAAA,MAAA;EAAY,MAAA,CAAA,EAxCZ,cAwCY,EAAA;AAQvB;AAKsB,UAlDL,iBAAA,CAkDoB;EAAA,KAAA,EAAA;IAC3B,OAAA,CAAA,EAAA,MAAA;IAEW,QAAA,CAAA,EAAA,MAAA;IAAlB,QAAA,CAAA,EAAA,MAAA;EAAO,CAAA;;WAlDC;;;UAIM,UAAA;;aAEJ;;UAGI,sBAAA;;;YAGL;;;eAGG;;iBAEE;;UAGA,YAAA;;gBAED;;UAGC,YAAA;;;YAGL;aACC;;QAEL;QACA;SACC;WACE;;;;;;;;;;;iBAQK,UAAA,sBAAgC;iBAK1B,eAAA,SACZ;;;IAEP;UAAkB"}
@@ -2,6 +2,7 @@ import { ChaosConfig, RecordConfig } from "./types.js";
2
2
  import { A2AAgentDefinition, A2AArtifact, A2APart, A2AStreamEvent } from "./a2a-types.js";
3
3
  import { LLMock } from "./llmock.js";
4
4
  import { MCPPromptDefinition, MCPToolDefinition } from "./mcp-types.js";
5
+ import { AGUIEvent } from "./agui-types.js";
5
6
  import { QueryResult } from "./vector-types.js";
6
7
 
7
8
  //#region src/config-loader.d.ts
@@ -53,6 +54,20 @@ interface A2AConfig {
53
54
  path?: string;
54
55
  agents?: A2AConfigAgent[];
55
56
  }
57
+ interface AGUIConfigFixture {
58
+ match: {
59
+ message?: string;
60
+ toolName?: string;
61
+ stateKey?: string;
62
+ };
63
+ text?: string;
64
+ events?: AGUIEvent[];
65
+ delayMs?: number;
66
+ }
67
+ interface AGUIConfig {
68
+ path?: string;
69
+ fixtures?: AGUIConfigFixture[];
70
+ }
56
71
  interface VectorConfigCollection {
57
72
  name: string;
58
73
  dimension: number;
@@ -75,6 +90,7 @@ interface AimockConfig {
75
90
  };
76
91
  mcp?: MCPConfig;
77
92
  a2a?: A2AConfig;
93
+ agui?: AGUIConfig;
78
94
  vector?: VectorConfig;
79
95
  services?: {
80
96
  search?: boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"config-loader.d.ts","names":[],"sources":["../src/config-loader.ts"],"sourcesContent":[],"mappings":";;;;;;;UAYiB,aAAA,SAAsB;;AAAvC;AAIiB,UAAA,iBAAA,CAAiB;EASjB,GAAA,EAAA,MAAA;EAAgB,IAAA,EAAA,MAAA;UAEnB,CAAA,EAAA,MAAA;aAF2B,CAAA,EAAA,MAAA;EAAmB,IAAA,CAAA,EAAA,MAAA;EAM3C,IAAA,CAAA,EAAA,MAAS;;AAGhB,UATO,eAAA,SAAwB,mBAS/B,CAAA;QACI,CAAA,EAAA;IACF,QAAA,EATE,KASF,CAAA;MAAe,IAAA,EAAA,MAAA;MAGV,OAAA,EAAA;QAAgB,IAAA,EAAA,MAAA;QAEvB,IAAA,EAAA,MAAA;MACI,CAAA;IACH,CAAA,CAAA;EAAc,CAAA;AAIzB;AAAgC,UAhBf,SAAA,CAgBe;MACnB,CAAA,EAAA,MAAA;YACH,CAAA,EAAA;IACS,IAAA,EAAA,MAAA;IAHqB,OAAA,EAAA,MAAA;EAAkB,CAAA;EAMzC,KAAA,CAAA,EAnBP,aAmBgB,EAEf;EAGM,SAAA,CAAA,EAvBH,iBAuByB,EAAA;EAAA,OAAA,CAAA,EAtB3B,eAsB2B,EAAA;;AAG3B,UAtBK,gBAAA,CAsBL;SAKK,EAAA,MAAA;EAAW,KAAA,CAAA,EAzBlB,OAyBkB,EAAA;EAGX,SAAA,CAAA,EA3BH,WA2Be,EAAA;EAKZ,MAAA,CAAA,EA/BN,cA+BkB,EAAA;EAAA,OAAA,CAAA,EAAA,MAAA;;AAIhB,UA/BI,cAAA,SAAuB,kBA+B3B,CAAA;UAEL,CAAA,EAhCK,gBAgCL,EAAA;OACA,CAAA,EAhCE,gBAgCF,EAAA;gBACG,CAAA,EAhCQ,gBAgCR,EAAA;;AAQK,UArCC,SAAA,CAqCS;EAKJ,IAAA,CAAA,EAAA,MAAA;EAAe,MAAA,CAAA,EAxC1B,cAwC0B,EAAA;;AAGhB,UAxCJ,sBAAA,CAwCI;MAAlB,EAAA,MAAA;EAAO,SAAA,EAAA,MAAA;YArCE;;;eAGG;;iBAEE;;UAGA,YAAA;;gBAED;;UAGC,YAAA;;;YAGL;aACC;;QAEL;QACA;WACG;;;;;;;;;;;iBAQK,UAAA,sBAAgC;iBAK1B,eAAA,SACZ;;;IAEP;UAAkB"}
1
+ {"version":3,"file":"config-loader.d.ts","names":[],"sources":["../src/config-loader.ts"],"sourcesContent":[],"mappings":";;;;;;;;UAciB,aAAA,SAAsB;;AAAvC;AAIiB,UAAA,iBAAA,CAAiB;EASjB,GAAA,EAAA,MAAA;EAAgB,IAAA,EAAA,MAAA;UAEnB,CAAA,EAAA,MAAA;aAF2B,CAAA,EAAA,MAAA;EAAmB,IAAA,CAAA,EAAA,MAAA;EAM3C,IAAA,CAAA,EAAA,MAAS;;AAGhB,UATO,eAAA,SAAwB,mBAS/B,CAAA;QACI,CAAA,EAAA;IACF,QAAA,EATE,KASF,CAAA;MAAe,IAAA,EAAA,MAAA;MAGV,OAAA,EAAA;QAAgB,IAAA,EAAA,MAAA;QAEvB,IAAA,EAAA,MAAA;MACI,CAAA;IACH,CAAA,CAAA;EAAc,CAAA;AAIzB;AAAgC,UAhBf,SAAA,CAgBe;MACnB,CAAA,EAAA,MAAA;YACH,CAAA,EAAA;IACS,IAAA,EAAA,MAAA;IAHqB,OAAA,EAAA,MAAA;EAAkB,CAAA;EAMzC,KAAA,CAAA,EAnBP,aAmBgB,EAEf;EAGM,SAAA,CAAA,EAvBH,iBAuBoB,EAGvB;EAIM,OAAA,CAAA,EA7BL,eA+BC,EAAA;AAGb;AAAuC,UA/BtB,gBAAA,CA+BsB;SAMxB,EAAA,MAAA;OAHH,CAAA,EAhCF,OAgCE,EAAA;WAKK,CAAA,EApCH,WAoCG,EAAA;EAAW,MAAA,CAAA,EAnCjB,cAmCiB,EAAA;EAGX,OAAA,CAAA,EAAA,MAAY;AAK7B;AAA6B,UAvCZ,cAAA,SAAuB,kBAuCX,CAAA;UAGjB,CAAA,EAzCC,gBAyCD,EAAA;OACC,CAAA,EAzCH,gBAyCG,EAAA;gBAEL,CAAA,EA1CW,gBA0CX,EAAA;;AAEC,UAzCQ,SAAA,CAyCR;MACE,CAAA,EAAA,MAAA;EAAY,MAAA,CAAA,EAxCZ,cAwCY,EAAA;AAQvB;AAKsB,UAlDL,iBAAA,CAkDoB;EAAA,KAAA,EAAA;IAC3B,OAAA,CAAA,EAAA,MAAA;IAEW,QAAA,CAAA,EAAA,MAAA;IAAlB,QAAA,CAAA,EAAA,MAAA;EAAO,CAAA;;WAlDC;;;UAIM,UAAA;;aAEJ;;UAGI,sBAAA;;;YAGL;;;eAGG;;iBAEE;;UAGA,YAAA;;gBAED;;UAGC,YAAA;;;YAGL;aACC;;QAEL;QACA;SACC;WACE;;;;;;;;;;;iBAQK,UAAA,sBAAgC;iBAK1B,eAAA,SACZ;;;IAEP;UAAkB"}
@@ -2,6 +2,7 @@ import { A2AMock } from "./a2a-mock.js";
2
2
  import { Logger } from "./logger.js";
3
3
  import { LLMock } from "./llmock.js";
4
4
  import { MCPMock } from "./mcp-mock.js";
5
+ import { AGUIMock } from "./agui-mock.js";
5
6
  import { VectorMock } from "./vector-mock.js";
6
7
  import * as path from "node:path";
7
8
  import * as fs from "node:fs";
@@ -67,6 +68,24 @@ async function startFromConfig(config, overrides) {
67
68
  llmock.mount(a2aPath, a2a);
68
69
  logger.info(`A2AMock mounted at ${a2aPath}`);
69
70
  }
71
+ if (config.agui) {
72
+ const aguiConfig = config.agui;
73
+ const agui = new AGUIMock();
74
+ if (aguiConfig.fixtures) for (const f of aguiConfig.fixtures) if (f.text) agui.onMessage(f.match.message ?? /.*/, f.text, { delayMs: f.delayMs });
75
+ else if (f.events) agui.addFixture({
76
+ match: {
77
+ message: f.match.message,
78
+ toolName: f.match.toolName,
79
+ stateKey: f.match.stateKey
80
+ },
81
+ events: f.events,
82
+ delayMs: f.delayMs
83
+ });
84
+ else logger.warn(`AG-UI fixture has neither text nor events — it will be skipped (match: ${JSON.stringify(f.match)})`);
85
+ const aguiPath = aguiConfig.path ?? "/agui";
86
+ llmock.mount(aguiPath, agui);
87
+ logger.info(`AGUIMock mounted at ${aguiPath}`);
88
+ }
70
89
  if (config.vector) {
71
90
  const vectorConfig = config.vector;
72
91
  const vector = new VectorMock();
@@ -1 +1 @@
1
- {"version":3,"file":"config-loader.js","names":[],"sources":["../src/config-loader.ts"],"sourcesContent":["import * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport { LLMock } from \"./llmock.js\";\nimport { MCPMock } from \"./mcp-mock.js\";\nimport { A2AMock } from \"./a2a-mock.js\";\nimport type { ChaosConfig, RecordConfig } from \"./types.js\";\nimport type { MCPToolDefinition, MCPPromptDefinition } from \"./mcp-types.js\";\nimport type { A2AAgentDefinition, A2APart, A2AArtifact, A2AStreamEvent } from \"./a2a-types.js\";\nimport { VectorMock } from \"./vector-mock.js\";\nimport type { QueryResult } from \"./vector-types.js\";\nimport { Logger } from \"./logger.js\";\n\nexport interface MCPConfigTool extends MCPToolDefinition {\n result?: string;\n}\n\nexport interface MCPConfigResource {\n uri: string;\n name: string;\n mimeType?: string;\n description?: string;\n text?: string;\n blob?: string;\n}\n\nexport interface MCPConfigPrompt extends MCPPromptDefinition {\n result?: {\n messages: Array<{ role: string; content: { type: string; text: string } }>;\n };\n}\n\nexport interface MCPConfig {\n path?: string;\n serverInfo?: { name: string; version: string };\n tools?: MCPConfigTool[];\n resources?: MCPConfigResource[];\n prompts?: MCPConfigPrompt[];\n}\n\nexport interface A2AConfigPattern {\n pattern: string;\n parts?: A2APart[];\n artifacts?: A2AArtifact[];\n events?: A2AStreamEvent[];\n delayMs?: number;\n}\n\nexport interface A2AConfigAgent extends A2AAgentDefinition {\n messages?: A2AConfigPattern[];\n tasks?: A2AConfigPattern[];\n streamingTasks?: A2AConfigPattern[];\n}\n\nexport interface A2AConfig {\n path?: string;\n agents?: A2AConfigAgent[];\n}\n\nexport interface VectorConfigCollection {\n name: string;\n dimension: number;\n vectors?: Array<{\n id: string;\n values: number[];\n metadata?: Record<string, unknown>;\n }>;\n queryResults?: QueryResult[];\n}\n\nexport interface VectorConfig {\n path?: string;\n collections?: VectorConfigCollection[];\n}\n\nexport interface AimockConfig {\n llm?: {\n fixtures?: string;\n chaos?: ChaosConfig;\n record?: RecordConfig;\n };\n mcp?: MCPConfig;\n a2a?: A2AConfig;\n vector?: VectorConfig;\n services?: { search?: boolean; rerank?: boolean; moderate?: boolean };\n metrics?: boolean;\n strict?: boolean;\n port?: number;\n host?: string;\n}\n\nexport function loadConfig(configPath: string): AimockConfig {\n const raw = fs.readFileSync(configPath, \"utf-8\");\n return JSON.parse(raw) as AimockConfig;\n}\n\nexport async function startFromConfig(\n config: AimockConfig,\n overrides?: { port?: number; host?: string },\n): Promise<{ llmock: LLMock; url: string }> {\n const logger = new Logger(\"info\");\n\n // Load fixtures if specified\n const llmock = new LLMock({\n port: overrides?.port ?? config.port ?? 0,\n host: overrides?.host ?? config.host ?? \"127.0.0.1\",\n chaos: config.llm?.chaos,\n record: config.llm?.record,\n metrics: config.metrics,\n strict: config.strict,\n });\n\n if (config.llm?.fixtures) {\n const fixturePath = path.resolve(config.llm.fixtures);\n const stat = fs.statSync(fixturePath);\n if (stat.isDirectory()) {\n llmock.loadFixtureDir(fixturePath);\n } else {\n llmock.loadFixtureFile(fixturePath);\n }\n }\n\n // MCP\n if (config.mcp) {\n const mcpConfig = config.mcp;\n const mcp = new MCPMock({\n serverInfo: mcpConfig.serverInfo,\n });\n\n if (mcpConfig.tools) {\n for (const tool of mcpConfig.tools) {\n const { result, ...def } = tool;\n mcp.addTool(def);\n if (result !== undefined) {\n mcp.onToolCall(def.name, () => result);\n }\n }\n }\n\n if (mcpConfig.resources) {\n for (const res of mcpConfig.resources) {\n mcp.addResource(\n { uri: res.uri, name: res.name, mimeType: res.mimeType, description: res.description },\n res.text !== undefined || res.blob !== undefined\n ? { text: res.text, blob: res.blob, mimeType: res.mimeType }\n : undefined,\n );\n }\n }\n\n if (mcpConfig.prompts) {\n for (const prompt of mcpConfig.prompts) {\n const { result, ...def } = prompt;\n if (result) {\n mcp.addPrompt(def, () => result as import(\"./mcp-types.js\").MCPPromptResult);\n } else {\n mcp.addPrompt(def);\n }\n }\n }\n\n const mcpPath = mcpConfig.path ?? \"/mcp\";\n llmock.mount(mcpPath, mcp);\n logger.info(`MCPMock mounted at ${mcpPath}`);\n }\n\n // A2A\n if (config.a2a) {\n const a2aConfig = config.a2a;\n const a2a = new A2AMock();\n\n if (a2aConfig.agents) {\n for (const agentConfig of a2aConfig.agents) {\n const { messages, tasks, streamingTasks, ...def } = agentConfig;\n a2a.registerAgent(def);\n\n if (messages) {\n for (const m of messages) {\n a2a.onMessage(def.name, m.pattern, m.parts ?? [{ text: \"\" }]);\n }\n }\n\n if (tasks) {\n for (const t of tasks) {\n a2a.onTask(def.name, t.pattern, t.artifacts ?? []);\n }\n }\n\n if (streamingTasks) {\n for (const s of streamingTasks) {\n a2a.onStreamingTask(def.name, s.pattern, s.events ?? [], s.delayMs);\n }\n }\n }\n }\n\n const a2aPath = a2aConfig.path ?? \"/a2a\";\n llmock.mount(a2aPath, a2a);\n logger.info(`A2AMock mounted at ${a2aPath}`);\n }\n\n // Vector\n if (config.vector) {\n const vectorConfig = config.vector;\n const vector = new VectorMock();\n\n if (vectorConfig.collections) {\n for (const col of vectorConfig.collections) {\n vector.addCollection(col.name, { dimension: col.dimension });\n\n if (col.vectors && col.vectors.length > 0) {\n vector.upsert(col.name, col.vectors);\n }\n\n if (col.queryResults) {\n vector.onQuery(col.name, col.queryResults);\n }\n }\n }\n\n const vectorPath = vectorConfig.path ?? \"/vector\";\n llmock.mount(vectorPath, vector);\n logger.info(`VectorMock mounted at ${vectorPath}`);\n }\n\n // Services — configure default catch-all responses\n if (config.services) {\n if (config.services.search) {\n llmock.onSearch(/.*/, []);\n logger.info(\"Search service enabled with default empty results\");\n }\n if (config.services.rerank) {\n llmock.onRerank(/.*/, []);\n logger.info(\"Rerank service enabled with default empty results\");\n }\n if (config.services.moderate) {\n llmock.onModerate(/.*/, { flagged: false, categories: {} });\n logger.info(\"Moderation service enabled with default unflagged results\");\n }\n }\n\n const url = await llmock.start();\n return { llmock, url };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"config-loader.js","names":[],"sources":["../src/config-loader.ts"],"sourcesContent":["import * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport { LLMock } from \"./llmock.js\";\nimport { MCPMock } from \"./mcp-mock.js\";\nimport { A2AMock } from \"./a2a-mock.js\";\nimport { AGUIMock } from \"./agui-mock.js\";\nimport type { ChaosConfig, RecordConfig } from \"./types.js\";\nimport type { MCPToolDefinition, MCPPromptDefinition } from \"./mcp-types.js\";\nimport type { A2AAgentDefinition, A2APart, A2AArtifact, A2AStreamEvent } from \"./a2a-types.js\";\nimport type { AGUIEvent } from \"./agui-types.js\";\nimport { VectorMock } from \"./vector-mock.js\";\nimport type { QueryResult } from \"./vector-types.js\";\nimport { Logger } from \"./logger.js\";\n\nexport interface MCPConfigTool extends MCPToolDefinition {\n result?: string;\n}\n\nexport interface MCPConfigResource {\n uri: string;\n name: string;\n mimeType?: string;\n description?: string;\n text?: string;\n blob?: string;\n}\n\nexport interface MCPConfigPrompt extends MCPPromptDefinition {\n result?: {\n messages: Array<{ role: string; content: { type: string; text: string } }>;\n };\n}\n\nexport interface MCPConfig {\n path?: string;\n serverInfo?: { name: string; version: string };\n tools?: MCPConfigTool[];\n resources?: MCPConfigResource[];\n prompts?: MCPConfigPrompt[];\n}\n\nexport interface A2AConfigPattern {\n pattern: string;\n parts?: A2APart[];\n artifacts?: A2AArtifact[];\n events?: A2AStreamEvent[];\n delayMs?: number;\n}\n\nexport interface A2AConfigAgent extends A2AAgentDefinition {\n messages?: A2AConfigPattern[];\n tasks?: A2AConfigPattern[];\n streamingTasks?: A2AConfigPattern[];\n}\n\nexport interface A2AConfig {\n path?: string;\n agents?: A2AConfigAgent[];\n}\n\nexport interface AGUIConfigFixture {\n match: { message?: string; toolName?: string; stateKey?: string };\n text?: string; // shorthand: uses buildTextResponse\n events?: AGUIEvent[]; // raw events\n delayMs?: number;\n}\n\nexport interface AGUIConfig {\n path?: string; // mount path, default \"/agui\"\n fixtures?: AGUIConfigFixture[];\n}\n\nexport interface VectorConfigCollection {\n name: string;\n dimension: number;\n vectors?: Array<{\n id: string;\n values: number[];\n metadata?: Record<string, unknown>;\n }>;\n queryResults?: QueryResult[];\n}\n\nexport interface VectorConfig {\n path?: string;\n collections?: VectorConfigCollection[];\n}\n\nexport interface AimockConfig {\n llm?: {\n fixtures?: string;\n chaos?: ChaosConfig;\n record?: RecordConfig;\n };\n mcp?: MCPConfig;\n a2a?: A2AConfig;\n agui?: AGUIConfig;\n vector?: VectorConfig;\n services?: { search?: boolean; rerank?: boolean; moderate?: boolean };\n metrics?: boolean;\n strict?: boolean;\n port?: number;\n host?: string;\n}\n\nexport function loadConfig(configPath: string): AimockConfig {\n const raw = fs.readFileSync(configPath, \"utf-8\");\n return JSON.parse(raw) as AimockConfig;\n}\n\nexport async function startFromConfig(\n config: AimockConfig,\n overrides?: { port?: number; host?: string },\n): Promise<{ llmock: LLMock; url: string }> {\n const logger = new Logger(\"info\");\n\n // Load fixtures if specified\n const llmock = new LLMock({\n port: overrides?.port ?? config.port ?? 0,\n host: overrides?.host ?? config.host ?? \"127.0.0.1\",\n chaos: config.llm?.chaos,\n record: config.llm?.record,\n metrics: config.metrics,\n strict: config.strict,\n });\n\n if (config.llm?.fixtures) {\n const fixturePath = path.resolve(config.llm.fixtures);\n const stat = fs.statSync(fixturePath);\n if (stat.isDirectory()) {\n llmock.loadFixtureDir(fixturePath);\n } else {\n llmock.loadFixtureFile(fixturePath);\n }\n }\n\n // MCP\n if (config.mcp) {\n const mcpConfig = config.mcp;\n const mcp = new MCPMock({\n serverInfo: mcpConfig.serverInfo,\n });\n\n if (mcpConfig.tools) {\n for (const tool of mcpConfig.tools) {\n const { result, ...def } = tool;\n mcp.addTool(def);\n if (result !== undefined) {\n mcp.onToolCall(def.name, () => result);\n }\n }\n }\n\n if (mcpConfig.resources) {\n for (const res of mcpConfig.resources) {\n mcp.addResource(\n { uri: res.uri, name: res.name, mimeType: res.mimeType, description: res.description },\n res.text !== undefined || res.blob !== undefined\n ? { text: res.text, blob: res.blob, mimeType: res.mimeType }\n : undefined,\n );\n }\n }\n\n if (mcpConfig.prompts) {\n for (const prompt of mcpConfig.prompts) {\n const { result, ...def } = prompt;\n if (result) {\n mcp.addPrompt(def, () => result as import(\"./mcp-types.js\").MCPPromptResult);\n } else {\n mcp.addPrompt(def);\n }\n }\n }\n\n const mcpPath = mcpConfig.path ?? \"/mcp\";\n llmock.mount(mcpPath, mcp);\n logger.info(`MCPMock mounted at ${mcpPath}`);\n }\n\n // A2A\n if (config.a2a) {\n const a2aConfig = config.a2a;\n const a2a = new A2AMock();\n\n if (a2aConfig.agents) {\n for (const agentConfig of a2aConfig.agents) {\n const { messages, tasks, streamingTasks, ...def } = agentConfig;\n a2a.registerAgent(def);\n\n if (messages) {\n for (const m of messages) {\n a2a.onMessage(def.name, m.pattern, m.parts ?? [{ text: \"\" }]);\n }\n }\n\n if (tasks) {\n for (const t of tasks) {\n a2a.onTask(def.name, t.pattern, t.artifacts ?? []);\n }\n }\n\n if (streamingTasks) {\n for (const s of streamingTasks) {\n a2a.onStreamingTask(def.name, s.pattern, s.events ?? [], s.delayMs);\n }\n }\n }\n }\n\n const a2aPath = a2aConfig.path ?? \"/a2a\";\n llmock.mount(a2aPath, a2a);\n logger.info(`A2AMock mounted at ${a2aPath}`);\n }\n\n // AG-UI\n if (config.agui) {\n const aguiConfig = config.agui;\n const agui = new AGUIMock();\n\n if (aguiConfig.fixtures) {\n for (const f of aguiConfig.fixtures) {\n if (f.text) {\n agui.onMessage(f.match.message ?? /.*/, f.text, { delayMs: f.delayMs });\n } else if (f.events) {\n agui.addFixture({\n match: {\n message: f.match.message,\n toolName: f.match.toolName,\n stateKey: f.match.stateKey,\n },\n events: f.events,\n delayMs: f.delayMs,\n });\n } else {\n logger.warn(\n `AG-UI fixture has neither text nor events — it will be skipped (match: ${JSON.stringify(f.match)})`,\n );\n }\n }\n }\n\n const aguiPath = aguiConfig.path ?? \"/agui\";\n llmock.mount(aguiPath, agui);\n logger.info(`AGUIMock mounted at ${aguiPath}`);\n }\n\n // Vector\n if (config.vector) {\n const vectorConfig = config.vector;\n const vector = new VectorMock();\n\n if (vectorConfig.collections) {\n for (const col of vectorConfig.collections) {\n vector.addCollection(col.name, { dimension: col.dimension });\n\n if (col.vectors && col.vectors.length > 0) {\n vector.upsert(col.name, col.vectors);\n }\n\n if (col.queryResults) {\n vector.onQuery(col.name, col.queryResults);\n }\n }\n }\n\n const vectorPath = vectorConfig.path ?? \"/vector\";\n llmock.mount(vectorPath, vector);\n logger.info(`VectorMock mounted at ${vectorPath}`);\n }\n\n // Services — configure default catch-all responses\n if (config.services) {\n if (config.services.search) {\n llmock.onSearch(/.*/, []);\n logger.info(\"Search service enabled with default empty results\");\n }\n if (config.services.rerank) {\n llmock.onRerank(/.*/, []);\n logger.info(\"Rerank service enabled with default empty results\");\n }\n if (config.services.moderate) {\n llmock.onModerate(/.*/, { flagged: false, categories: {} });\n logger.info(\"Moderation service enabled with default unflagged results\");\n }\n }\n\n const url = await llmock.start();\n return { llmock, url };\n}\n"],"mappings":"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"}
@@ -34,7 +34,8 @@ async function handleEmbeddings(req, res, raw, fixtures, journal, defaults, setC
34
34
  const syntheticReq = {
35
35
  model: embeddingReq.model,
36
36
  messages: [],
37
- embeddingInput: combinedInput
37
+ embeddingInput: combinedInput,
38
+ _endpointType: "embedding"
38
39
  };
39
40
  const testId = require_helpers.getTestId(req);
40
41
  const fixture = require_router.matchFixture(fixtures, syntheticReq, journal.getFixtureMatchCountsForTest(testId), defaults.requestTransform);
@@ -1 +1 @@
1
- {"version":3,"file":"embeddings.cjs","names":["flattenHeaders","getTestId","matchFixture","applyChaos","isErrorResponse","isEmbeddingResponse","buildEmbeddingResponse","proxyAndRecord","generateDeterministicEmbedding"],"sources":["../src/embeddings.ts"],"sourcesContent":["/**\n * OpenAI Embeddings API support for LLMock.\n *\n * Handles POST /v1/embeddings requests. Matches fixtures using the `inputText`\n * field, and falls back to generating a deterministic embedding from the input\n * text hash when no fixture matches.\n */\n\nimport type * as http from \"node:http\";\nimport type { ChatCompletionRequest, Fixture, HandlerDefaults } from \"./types.js\";\nimport {\n isEmbeddingResponse,\n isErrorResponse,\n generateDeterministicEmbedding,\n buildEmbeddingResponse,\n flattenHeaders,\n getTestId,\n} from \"./helpers.js\";\nimport { matchFixture } from \"./router.js\";\nimport { writeErrorResponse } from \"./sse-writer.js\";\nimport type { Journal } from \"./journal.js\";\nimport { applyChaos } from \"./chaos.js\";\nimport { proxyAndRecord } from \"./recorder.js\";\n\n// ─── Embeddings API request types ──────────────────────────────────────────\n\ninterface EmbeddingRequest {\n input: string | string[];\n model: string;\n encoding_format?: \"float\" | \"base64\";\n dimensions?: number;\n [key: string]: unknown;\n}\n\n// ─── Request handler ───────────────────────────────────────────────────────\n\nexport async function handleEmbeddings(\n req: http.IncomingMessage,\n res: http.ServerResponse,\n raw: string,\n fixtures: Fixture[],\n journal: Journal,\n defaults: HandlerDefaults,\n setCorsHeaders: (res: http.ServerResponse) => void,\n): Promise<void> {\n const { logger } = defaults;\n setCorsHeaders(res);\n\n let embeddingReq: EmbeddingRequest;\n try {\n embeddingReq = JSON.parse(raw) as EmbeddingRequest;\n } catch {\n journal.add({\n method: req.method ?? \"POST\",\n path: req.url ?? \"/v1/embeddings\",\n headers: flattenHeaders(req.headers),\n body: null,\n response: { status: 400, fixture: null },\n });\n writeErrorResponse(\n res,\n 400,\n JSON.stringify({\n error: {\n message: \"Malformed JSON\",\n type: \"invalid_request_error\",\n code: \"invalid_json\",\n },\n }),\n );\n return;\n }\n\n // Normalize input to array of strings\n const inputs: string[] = Array.isArray(embeddingReq.input)\n ? embeddingReq.input\n : [embeddingReq.input];\n\n // Concatenate all inputs for matching purposes\n const combinedInput = inputs.join(\" \");\n\n // Build a synthetic ChatCompletionRequest for the fixture router.\n // We attach `embeddingInput` so the router's inputText matching can use it.\n const syntheticReq: ChatCompletionRequest = {\n model: embeddingReq.model,\n messages: [],\n embeddingInput: combinedInput,\n };\n\n const testId = getTestId(req);\n const fixture = matchFixture(\n fixtures,\n syntheticReq,\n journal.getFixtureMatchCountsForTest(testId),\n defaults.requestTransform,\n );\n\n if (fixture) {\n journal.incrementFixtureMatchCount(fixture, fixtures, testId);\n }\n\n if (\n applyChaos(\n res,\n fixture,\n defaults.chaos,\n req.headers,\n journal,\n {\n method: req.method ?? \"POST\",\n path: req.url ?? \"/v1/embeddings\",\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n },\n defaults.registry,\n defaults.logger,\n )\n )\n return;\n\n if (fixture) {\n const response = fixture.response;\n\n // Error response\n if (isErrorResponse(response)) {\n const status = response.status ?? 500;\n journal.add({\n method: req.method ?? \"POST\",\n path: req.url ?? \"/v1/embeddings\",\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: { status, fixture },\n });\n writeErrorResponse(res, status, JSON.stringify(response));\n return;\n }\n\n // Embedding response — use the fixture's embedding for each input\n if (isEmbeddingResponse(response)) {\n journal.add({\n method: req.method ?? \"POST\",\n path: req.url ?? \"/v1/embeddings\",\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: { status: 200, fixture },\n });\n const embeddings = inputs.map(() => [...response.embedding]);\n const body = buildEmbeddingResponse(embeddings, embeddingReq.model);\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify(body));\n return;\n }\n\n // Fixture matched but response type is not compatible with embeddings\n journal.add({\n method: req.method ?? \"POST\",\n path: req.url ?? \"/v1/embeddings\",\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: { status: 500, fixture },\n });\n writeErrorResponse(\n res,\n 500,\n JSON.stringify({\n error: {\n message:\n \"Fixture response did not match any known embedding type (must have embedding or error)\",\n type: \"server_error\",\n },\n }),\n );\n return;\n }\n\n // No fixture match — try record-and-replay proxy if configured\n if (defaults.record) {\n const proxied = await proxyAndRecord(\n req,\n res,\n syntheticReq,\n \"openai\",\n req.url ?? \"/v1/embeddings\",\n fixtures,\n defaults,\n raw,\n );\n if (proxied) {\n journal.add({\n method: req.method ?? \"POST\",\n path: req.url ?? \"/v1/embeddings\",\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: { status: res.statusCode ?? 200, fixture: null },\n });\n return;\n }\n }\n\n if (defaults.strict) {\n logger.error(\n `STRICT: No fixture matched for ${req.method ?? \"POST\"} ${req.url ?? \"/v1/embeddings\"}`,\n );\n journal.add({\n method: req.method ?? \"POST\",\n path: req.url ?? \"/v1/embeddings\",\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: { status: 503, fixture: null },\n });\n writeErrorResponse(\n res,\n 503,\n JSON.stringify({\n error: {\n message: \"Strict mode: no fixture matched\",\n type: \"invalid_request_error\",\n code: \"no_fixture_match\",\n },\n }),\n );\n return;\n }\n\n // No fixture match — generate deterministic embeddings from input text\n logger.warn(\n `No embedding fixture matched for \"${combinedInput.slice(0, 80)}\" — returning deterministic fallback`,\n );\n const dimensions = embeddingReq.dimensions ?? 1536;\n const embeddings = inputs.map((input) => generateDeterministicEmbedding(input, dimensions));\n\n journal.add({\n method: req.method ?? \"POST\",\n path: req.url ?? \"/v1/embeddings\",\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: { status: 200, fixture: null },\n });\n\n const body = buildEmbeddingResponse(embeddings, embeddingReq.model);\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify(body));\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"embeddings.cjs","names":["flattenHeaders","getTestId","matchFixture","applyChaos","isErrorResponse","isEmbeddingResponse","buildEmbeddingResponse","proxyAndRecord","generateDeterministicEmbedding"],"sources":["../src/embeddings.ts"],"sourcesContent":["/**\n * OpenAI Embeddings API support for LLMock.\n *\n * Handles POST /v1/embeddings requests. Matches fixtures using the `inputText`\n * field, and falls back to generating a deterministic embedding from the input\n * text hash when no fixture matches.\n */\n\nimport type * as http from \"node:http\";\nimport type { ChatCompletionRequest, Fixture, HandlerDefaults } from \"./types.js\";\nimport {\n isEmbeddingResponse,\n isErrorResponse,\n generateDeterministicEmbedding,\n buildEmbeddingResponse,\n flattenHeaders,\n getTestId,\n} from \"./helpers.js\";\nimport { matchFixture } from \"./router.js\";\nimport { writeErrorResponse } from \"./sse-writer.js\";\nimport type { Journal } from \"./journal.js\";\nimport { applyChaos } from \"./chaos.js\";\nimport { proxyAndRecord } from \"./recorder.js\";\n\n// ─── Embeddings API request types ──────────────────────────────────────────\n\ninterface EmbeddingRequest {\n input: string | string[];\n model: string;\n encoding_format?: \"float\" | \"base64\";\n dimensions?: number;\n [key: string]: unknown;\n}\n\n// ─── Request handler ───────────────────────────────────────────────────────\n\nexport async function handleEmbeddings(\n req: http.IncomingMessage,\n res: http.ServerResponse,\n raw: string,\n fixtures: Fixture[],\n journal: Journal,\n defaults: HandlerDefaults,\n setCorsHeaders: (res: http.ServerResponse) => void,\n): Promise<void> {\n const { logger } = defaults;\n setCorsHeaders(res);\n\n let embeddingReq: EmbeddingRequest;\n try {\n embeddingReq = JSON.parse(raw) as EmbeddingRequest;\n } catch {\n journal.add({\n method: req.method ?? \"POST\",\n path: req.url ?? \"/v1/embeddings\",\n headers: flattenHeaders(req.headers),\n body: null,\n response: { status: 400, fixture: null },\n });\n writeErrorResponse(\n res,\n 400,\n JSON.stringify({\n error: {\n message: \"Malformed JSON\",\n type: \"invalid_request_error\",\n code: \"invalid_json\",\n },\n }),\n );\n return;\n }\n\n // Normalize input to array of strings\n const inputs: string[] = Array.isArray(embeddingReq.input)\n ? embeddingReq.input\n : [embeddingReq.input];\n\n // Concatenate all inputs for matching purposes\n const combinedInput = inputs.join(\" \");\n\n // Build a synthetic ChatCompletionRequest for the fixture router.\n // We attach `embeddingInput` so the router's inputText matching can use it.\n const syntheticReq: ChatCompletionRequest = {\n model: embeddingReq.model,\n messages: [],\n embeddingInput: combinedInput,\n _endpointType: \"embedding\",\n };\n\n const testId = getTestId(req);\n const fixture = matchFixture(\n fixtures,\n syntheticReq,\n journal.getFixtureMatchCountsForTest(testId),\n defaults.requestTransform,\n );\n\n if (fixture) {\n journal.incrementFixtureMatchCount(fixture, fixtures, testId);\n }\n\n if (\n applyChaos(\n res,\n fixture,\n defaults.chaos,\n req.headers,\n journal,\n {\n method: req.method ?? \"POST\",\n path: req.url ?? \"/v1/embeddings\",\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n },\n defaults.registry,\n defaults.logger,\n )\n )\n return;\n\n if (fixture) {\n const response = fixture.response;\n\n // Error response\n if (isErrorResponse(response)) {\n const status = response.status ?? 500;\n journal.add({\n method: req.method ?? \"POST\",\n path: req.url ?? \"/v1/embeddings\",\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: { status, fixture },\n });\n writeErrorResponse(res, status, JSON.stringify(response));\n return;\n }\n\n // Embedding response — use the fixture's embedding for each input\n if (isEmbeddingResponse(response)) {\n journal.add({\n method: req.method ?? \"POST\",\n path: req.url ?? \"/v1/embeddings\",\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: { status: 200, fixture },\n });\n const embeddings = inputs.map(() => [...response.embedding]);\n const body = buildEmbeddingResponse(embeddings, embeddingReq.model);\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify(body));\n return;\n }\n\n // Fixture matched but response type is not compatible with embeddings\n journal.add({\n method: req.method ?? \"POST\",\n path: req.url ?? \"/v1/embeddings\",\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: { status: 500, fixture },\n });\n writeErrorResponse(\n res,\n 500,\n JSON.stringify({\n error: {\n message:\n \"Fixture response did not match any known embedding type (must have embedding or error)\",\n type: \"server_error\",\n },\n }),\n );\n return;\n }\n\n // No fixture match — try record-and-replay proxy if configured\n if (defaults.record) {\n const proxied = await proxyAndRecord(\n req,\n res,\n syntheticReq,\n \"openai\",\n req.url ?? \"/v1/embeddings\",\n fixtures,\n defaults,\n raw,\n );\n if (proxied) {\n journal.add({\n method: req.method ?? \"POST\",\n path: req.url ?? \"/v1/embeddings\",\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: { status: res.statusCode ?? 200, fixture: null },\n });\n return;\n }\n }\n\n if (defaults.strict) {\n logger.error(\n `STRICT: No fixture matched for ${req.method ?? \"POST\"} ${req.url ?? \"/v1/embeddings\"}`,\n );\n journal.add({\n method: req.method ?? \"POST\",\n path: req.url ?? \"/v1/embeddings\",\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: { status: 503, fixture: null },\n });\n writeErrorResponse(\n res,\n 503,\n JSON.stringify({\n error: {\n message: \"Strict mode: no fixture matched\",\n type: \"invalid_request_error\",\n code: \"no_fixture_match\",\n },\n }),\n );\n return;\n }\n\n // No fixture match — generate deterministic embeddings from input text\n logger.warn(\n `No embedding fixture matched for \"${combinedInput.slice(0, 80)}\" — returning deterministic fallback`,\n );\n const dimensions = embeddingReq.dimensions ?? 1536;\n const embeddings = inputs.map((input) => generateDeterministicEmbedding(input, dimensions));\n\n journal.add({\n method: req.method ?? \"POST\",\n path: req.url ?? \"/v1/embeddings\",\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: { status: 200, fixture: null },\n });\n\n const body = buildEmbeddingResponse(embeddings, embeddingReq.model);\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify(body));\n}\n"],"mappings":"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"}
@@ -34,7 +34,8 @@ async function handleEmbeddings(req, res, raw, fixtures, journal, defaults, setC
34
34
  const syntheticReq = {
35
35
  model: embeddingReq.model,
36
36
  messages: [],
37
- embeddingInput: combinedInput
37
+ embeddingInput: combinedInput,
38
+ _endpointType: "embedding"
38
39
  };
39
40
  const testId = getTestId(req);
40
41
  const fixture = matchFixture(fixtures, syntheticReq, journal.getFixtureMatchCountsForTest(testId), defaults.requestTransform);
@@ -1 +1 @@
1
- {"version":3,"file":"embeddings.js","names":[],"sources":["../src/embeddings.ts"],"sourcesContent":["/**\n * OpenAI Embeddings API support for LLMock.\n *\n * Handles POST /v1/embeddings requests. Matches fixtures using the `inputText`\n * field, and falls back to generating a deterministic embedding from the input\n * text hash when no fixture matches.\n */\n\nimport type * as http from \"node:http\";\nimport type { ChatCompletionRequest, Fixture, HandlerDefaults } from \"./types.js\";\nimport {\n isEmbeddingResponse,\n isErrorResponse,\n generateDeterministicEmbedding,\n buildEmbeddingResponse,\n flattenHeaders,\n getTestId,\n} from \"./helpers.js\";\nimport { matchFixture } from \"./router.js\";\nimport { writeErrorResponse } from \"./sse-writer.js\";\nimport type { Journal } from \"./journal.js\";\nimport { applyChaos } from \"./chaos.js\";\nimport { proxyAndRecord } from \"./recorder.js\";\n\n// ─── Embeddings API request types ──────────────────────────────────────────\n\ninterface EmbeddingRequest {\n input: string | string[];\n model: string;\n encoding_format?: \"float\" | \"base64\";\n dimensions?: number;\n [key: string]: unknown;\n}\n\n// ─── Request handler ───────────────────────────────────────────────────────\n\nexport async function handleEmbeddings(\n req: http.IncomingMessage,\n res: http.ServerResponse,\n raw: string,\n fixtures: Fixture[],\n journal: Journal,\n defaults: HandlerDefaults,\n setCorsHeaders: (res: http.ServerResponse) => void,\n): Promise<void> {\n const { logger } = defaults;\n setCorsHeaders(res);\n\n let embeddingReq: EmbeddingRequest;\n try {\n embeddingReq = JSON.parse(raw) as EmbeddingRequest;\n } catch {\n journal.add({\n method: req.method ?? \"POST\",\n path: req.url ?? \"/v1/embeddings\",\n headers: flattenHeaders(req.headers),\n body: null,\n response: { status: 400, fixture: null },\n });\n writeErrorResponse(\n res,\n 400,\n JSON.stringify({\n error: {\n message: \"Malformed JSON\",\n type: \"invalid_request_error\",\n code: \"invalid_json\",\n },\n }),\n );\n return;\n }\n\n // Normalize input to array of strings\n const inputs: string[] = Array.isArray(embeddingReq.input)\n ? embeddingReq.input\n : [embeddingReq.input];\n\n // Concatenate all inputs for matching purposes\n const combinedInput = inputs.join(\" \");\n\n // Build a synthetic ChatCompletionRequest for the fixture router.\n // We attach `embeddingInput` so the router's inputText matching can use it.\n const syntheticReq: ChatCompletionRequest = {\n model: embeddingReq.model,\n messages: [],\n embeddingInput: combinedInput,\n };\n\n const testId = getTestId(req);\n const fixture = matchFixture(\n fixtures,\n syntheticReq,\n journal.getFixtureMatchCountsForTest(testId),\n defaults.requestTransform,\n );\n\n if (fixture) {\n journal.incrementFixtureMatchCount(fixture, fixtures, testId);\n }\n\n if (\n applyChaos(\n res,\n fixture,\n defaults.chaos,\n req.headers,\n journal,\n {\n method: req.method ?? \"POST\",\n path: req.url ?? \"/v1/embeddings\",\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n },\n defaults.registry,\n defaults.logger,\n )\n )\n return;\n\n if (fixture) {\n const response = fixture.response;\n\n // Error response\n if (isErrorResponse(response)) {\n const status = response.status ?? 500;\n journal.add({\n method: req.method ?? \"POST\",\n path: req.url ?? \"/v1/embeddings\",\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: { status, fixture },\n });\n writeErrorResponse(res, status, JSON.stringify(response));\n return;\n }\n\n // Embedding response — use the fixture's embedding for each input\n if (isEmbeddingResponse(response)) {\n journal.add({\n method: req.method ?? \"POST\",\n path: req.url ?? \"/v1/embeddings\",\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: { status: 200, fixture },\n });\n const embeddings = inputs.map(() => [...response.embedding]);\n const body = buildEmbeddingResponse(embeddings, embeddingReq.model);\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify(body));\n return;\n }\n\n // Fixture matched but response type is not compatible with embeddings\n journal.add({\n method: req.method ?? \"POST\",\n path: req.url ?? \"/v1/embeddings\",\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: { status: 500, fixture },\n });\n writeErrorResponse(\n res,\n 500,\n JSON.stringify({\n error: {\n message:\n \"Fixture response did not match any known embedding type (must have embedding or error)\",\n type: \"server_error\",\n },\n }),\n );\n return;\n }\n\n // No fixture match — try record-and-replay proxy if configured\n if (defaults.record) {\n const proxied = await proxyAndRecord(\n req,\n res,\n syntheticReq,\n \"openai\",\n req.url ?? \"/v1/embeddings\",\n fixtures,\n defaults,\n raw,\n );\n if (proxied) {\n journal.add({\n method: req.method ?? \"POST\",\n path: req.url ?? \"/v1/embeddings\",\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: { status: res.statusCode ?? 200, fixture: null },\n });\n return;\n }\n }\n\n if (defaults.strict) {\n logger.error(\n `STRICT: No fixture matched for ${req.method ?? \"POST\"} ${req.url ?? \"/v1/embeddings\"}`,\n );\n journal.add({\n method: req.method ?? \"POST\",\n path: req.url ?? \"/v1/embeddings\",\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: { status: 503, fixture: null },\n });\n writeErrorResponse(\n res,\n 503,\n JSON.stringify({\n error: {\n message: \"Strict mode: no fixture matched\",\n type: \"invalid_request_error\",\n code: \"no_fixture_match\",\n },\n }),\n );\n return;\n }\n\n // No fixture match — generate deterministic embeddings from input text\n logger.warn(\n `No embedding fixture matched for \"${combinedInput.slice(0, 80)}\" — returning deterministic fallback`,\n );\n const dimensions = embeddingReq.dimensions ?? 1536;\n const embeddings = inputs.map((input) => generateDeterministicEmbedding(input, dimensions));\n\n journal.add({\n method: req.method ?? \"POST\",\n path: req.url ?? \"/v1/embeddings\",\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: { status: 200, fixture: null },\n });\n\n const body = buildEmbeddingResponse(embeddings, embeddingReq.model);\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify(body));\n}\n"],"mappings":";;;;;;;AAoCA,eAAsB,iBACpB,KACA,KACA,KACA,UACA,SACA,UACA,gBACe;CACf,MAAM,EAAE,WAAW;AACnB,gBAAe,IAAI;CAEnB,IAAI;AACJ,KAAI;AACF,iBAAe,KAAK,MAAM,IAAI;SACxB;AACN,UAAQ,IAAI;GACV,QAAQ,IAAI,UAAU;GACtB,MAAM,IAAI,OAAO;GACjB,SAAS,eAAe,IAAI,QAAQ;GACpC,MAAM;GACN,UAAU;IAAE,QAAQ;IAAK,SAAS;IAAM;GACzC,CAAC;AACF,qBACE,KACA,KACA,KAAK,UAAU,EACb,OAAO;GACL,SAAS;GACT,MAAM;GACN,MAAM;GACP,EACF,CAAC,CACH;AACD;;CAIF,MAAM,SAAmB,MAAM,QAAQ,aAAa,MAAM,GACtD,aAAa,QACb,CAAC,aAAa,MAAM;CAGxB,MAAM,gBAAgB,OAAO,KAAK,IAAI;CAItC,MAAM,eAAsC;EAC1C,OAAO,aAAa;EACpB,UAAU,EAAE;EACZ,gBAAgB;EACjB;CAED,MAAM,SAAS,UAAU,IAAI;CAC7B,MAAM,UAAU,aACd,UACA,cACA,QAAQ,6BAA6B,OAAO,EAC5C,SAAS,iBACV;AAED,KAAI,QACF,SAAQ,2BAA2B,SAAS,UAAU,OAAO;AAG/D,KACE,WACE,KACA,SACA,SAAS,OACT,IAAI,SACJ,SACA;EACE,QAAQ,IAAI,UAAU;EACtB,MAAM,IAAI,OAAO;EACjB,SAAS,eAAe,IAAI,QAAQ;EACpC,MAAM;EACP,EACD,SAAS,UACT,SAAS,OACV,CAED;AAEF,KAAI,SAAS;EACX,MAAM,WAAW,QAAQ;AAGzB,MAAI,gBAAgB,SAAS,EAAE;GAC7B,MAAM,SAAS,SAAS,UAAU;AAClC,WAAQ,IAAI;IACV,QAAQ,IAAI,UAAU;IACtB,MAAM,IAAI,OAAO;IACjB,SAAS,eAAe,IAAI,QAAQ;IACpC,MAAM;IACN,UAAU;KAAE;KAAQ;KAAS;IAC9B,CAAC;AACF,sBAAmB,KAAK,QAAQ,KAAK,UAAU,SAAS,CAAC;AACzD;;AAIF,MAAI,oBAAoB,SAAS,EAAE;AACjC,WAAQ,IAAI;IACV,QAAQ,IAAI,UAAU;IACtB,MAAM,IAAI,OAAO;IACjB,SAAS,eAAe,IAAI,QAAQ;IACpC,MAAM;IACN,UAAU;KAAE,QAAQ;KAAK;KAAS;IACnC,CAAC;GAEF,MAAM,OAAO,uBADM,OAAO,UAAU,CAAC,GAAG,SAAS,UAAU,CAAC,EACZ,aAAa,MAAM;AACnE,OAAI,UAAU,KAAK,EAAE,gBAAgB,oBAAoB,CAAC;AAC1D,OAAI,IAAI,KAAK,UAAU,KAAK,CAAC;AAC7B;;AAIF,UAAQ,IAAI;GACV,QAAQ,IAAI,UAAU;GACtB,MAAM,IAAI,OAAO;GACjB,SAAS,eAAe,IAAI,QAAQ;GACpC,MAAM;GACN,UAAU;IAAE,QAAQ;IAAK;IAAS;GACnC,CAAC;AACF,qBACE,KACA,KACA,KAAK,UAAU,EACb,OAAO;GACL,SACE;GACF,MAAM;GACP,EACF,CAAC,CACH;AACD;;AAIF,KAAI,SAAS,QAWX;MAVgB,MAAM,eACpB,KACA,KACA,cACA,UACA,IAAI,OAAO,kBACX,UACA,UACA,IACD,EACY;AACX,WAAQ,IAAI;IACV,QAAQ,IAAI,UAAU;IACtB,MAAM,IAAI,OAAO;IACjB,SAAS,eAAe,IAAI,QAAQ;IACpC,MAAM;IACN,UAAU;KAAE,QAAQ,IAAI,cAAc;KAAK,SAAS;KAAM;IAC3D,CAAC;AACF;;;AAIJ,KAAI,SAAS,QAAQ;AACnB,SAAO,MACL,kCAAkC,IAAI,UAAU,OAAO,GAAG,IAAI,OAAO,mBACtE;AACD,UAAQ,IAAI;GACV,QAAQ,IAAI,UAAU;GACtB,MAAM,IAAI,OAAO;GACjB,SAAS,eAAe,IAAI,QAAQ;GACpC,MAAM;GACN,UAAU;IAAE,QAAQ;IAAK,SAAS;IAAM;GACzC,CAAC;AACF,qBACE,KACA,KACA,KAAK,UAAU,EACb,OAAO;GACL,SAAS;GACT,MAAM;GACN,MAAM;GACP,EACF,CAAC,CACH;AACD;;AAIF,QAAO,KACL,qCAAqC,cAAc,MAAM,GAAG,GAAG,CAAC,sCACjE;CACD,MAAM,aAAa,aAAa,cAAc;CAC9C,MAAM,aAAa,OAAO,KAAK,UAAU,+BAA+B,OAAO,WAAW,CAAC;AAE3F,SAAQ,IAAI;EACV,QAAQ,IAAI,UAAU;EACtB,MAAM,IAAI,OAAO;EACjB,SAAS,eAAe,IAAI,QAAQ;EACpC,MAAM;EACN,UAAU;GAAE,QAAQ;GAAK,SAAS;GAAM;EACzC,CAAC;CAEF,MAAM,OAAO,uBAAuB,YAAY,aAAa,MAAM;AACnE,KAAI,UAAU,KAAK,EAAE,gBAAgB,oBAAoB,CAAC;AAC1D,KAAI,IAAI,KAAK,UAAU,KAAK,CAAC"}
1
+ {"version":3,"file":"embeddings.js","names":[],"sources":["../src/embeddings.ts"],"sourcesContent":["/**\n * OpenAI Embeddings API support for LLMock.\n *\n * Handles POST /v1/embeddings requests. Matches fixtures using the `inputText`\n * field, and falls back to generating a deterministic embedding from the input\n * text hash when no fixture matches.\n */\n\nimport type * as http from \"node:http\";\nimport type { ChatCompletionRequest, Fixture, HandlerDefaults } from \"./types.js\";\nimport {\n isEmbeddingResponse,\n isErrorResponse,\n generateDeterministicEmbedding,\n buildEmbeddingResponse,\n flattenHeaders,\n getTestId,\n} from \"./helpers.js\";\nimport { matchFixture } from \"./router.js\";\nimport { writeErrorResponse } from \"./sse-writer.js\";\nimport type { Journal } from \"./journal.js\";\nimport { applyChaos } from \"./chaos.js\";\nimport { proxyAndRecord } from \"./recorder.js\";\n\n// ─── Embeddings API request types ──────────────────────────────────────────\n\ninterface EmbeddingRequest {\n input: string | string[];\n model: string;\n encoding_format?: \"float\" | \"base64\";\n dimensions?: number;\n [key: string]: unknown;\n}\n\n// ─── Request handler ───────────────────────────────────────────────────────\n\nexport async function handleEmbeddings(\n req: http.IncomingMessage,\n res: http.ServerResponse,\n raw: string,\n fixtures: Fixture[],\n journal: Journal,\n defaults: HandlerDefaults,\n setCorsHeaders: (res: http.ServerResponse) => void,\n): Promise<void> {\n const { logger } = defaults;\n setCorsHeaders(res);\n\n let embeddingReq: EmbeddingRequest;\n try {\n embeddingReq = JSON.parse(raw) as EmbeddingRequest;\n } catch {\n journal.add({\n method: req.method ?? \"POST\",\n path: req.url ?? \"/v1/embeddings\",\n headers: flattenHeaders(req.headers),\n body: null,\n response: { status: 400, fixture: null },\n });\n writeErrorResponse(\n res,\n 400,\n JSON.stringify({\n error: {\n message: \"Malformed JSON\",\n type: \"invalid_request_error\",\n code: \"invalid_json\",\n },\n }),\n );\n return;\n }\n\n // Normalize input to array of strings\n const inputs: string[] = Array.isArray(embeddingReq.input)\n ? embeddingReq.input\n : [embeddingReq.input];\n\n // Concatenate all inputs for matching purposes\n const combinedInput = inputs.join(\" \");\n\n // Build a synthetic ChatCompletionRequest for the fixture router.\n // We attach `embeddingInput` so the router's inputText matching can use it.\n const syntheticReq: ChatCompletionRequest = {\n model: embeddingReq.model,\n messages: [],\n embeddingInput: combinedInput,\n _endpointType: \"embedding\",\n };\n\n const testId = getTestId(req);\n const fixture = matchFixture(\n fixtures,\n syntheticReq,\n journal.getFixtureMatchCountsForTest(testId),\n defaults.requestTransform,\n );\n\n if (fixture) {\n journal.incrementFixtureMatchCount(fixture, fixtures, testId);\n }\n\n if (\n applyChaos(\n res,\n fixture,\n defaults.chaos,\n req.headers,\n journal,\n {\n method: req.method ?? \"POST\",\n path: req.url ?? \"/v1/embeddings\",\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n },\n defaults.registry,\n defaults.logger,\n )\n )\n return;\n\n if (fixture) {\n const response = fixture.response;\n\n // Error response\n if (isErrorResponse(response)) {\n const status = response.status ?? 500;\n journal.add({\n method: req.method ?? \"POST\",\n path: req.url ?? \"/v1/embeddings\",\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: { status, fixture },\n });\n writeErrorResponse(res, status, JSON.stringify(response));\n return;\n }\n\n // Embedding response — use the fixture's embedding for each input\n if (isEmbeddingResponse(response)) {\n journal.add({\n method: req.method ?? \"POST\",\n path: req.url ?? \"/v1/embeddings\",\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: { status: 200, fixture },\n });\n const embeddings = inputs.map(() => [...response.embedding]);\n const body = buildEmbeddingResponse(embeddings, embeddingReq.model);\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify(body));\n return;\n }\n\n // Fixture matched but response type is not compatible with embeddings\n journal.add({\n method: req.method ?? \"POST\",\n path: req.url ?? \"/v1/embeddings\",\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: { status: 500, fixture },\n });\n writeErrorResponse(\n res,\n 500,\n JSON.stringify({\n error: {\n message:\n \"Fixture response did not match any known embedding type (must have embedding or error)\",\n type: \"server_error\",\n },\n }),\n );\n return;\n }\n\n // No fixture match — try record-and-replay proxy if configured\n if (defaults.record) {\n const proxied = await proxyAndRecord(\n req,\n res,\n syntheticReq,\n \"openai\",\n req.url ?? \"/v1/embeddings\",\n fixtures,\n defaults,\n raw,\n );\n if (proxied) {\n journal.add({\n method: req.method ?? \"POST\",\n path: req.url ?? \"/v1/embeddings\",\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: { status: res.statusCode ?? 200, fixture: null },\n });\n return;\n }\n }\n\n if (defaults.strict) {\n logger.error(\n `STRICT: No fixture matched for ${req.method ?? \"POST\"} ${req.url ?? \"/v1/embeddings\"}`,\n );\n journal.add({\n method: req.method ?? \"POST\",\n path: req.url ?? \"/v1/embeddings\",\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: { status: 503, fixture: null },\n });\n writeErrorResponse(\n res,\n 503,\n JSON.stringify({\n error: {\n message: \"Strict mode: no fixture matched\",\n type: \"invalid_request_error\",\n code: \"no_fixture_match\",\n },\n }),\n );\n return;\n }\n\n // No fixture match — generate deterministic embeddings from input text\n logger.warn(\n `No embedding fixture matched for \"${combinedInput.slice(0, 80)}\" — returning deterministic fallback`,\n );\n const dimensions = embeddingReq.dimensions ?? 1536;\n const embeddings = inputs.map((input) => generateDeterministicEmbedding(input, dimensions));\n\n journal.add({\n method: req.method ?? \"POST\",\n path: req.url ?? \"/v1/embeddings\",\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: { status: 200, fixture: null },\n });\n\n const body = buildEmbeddingResponse(embeddings, embeddingReq.model);\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify(body));\n}\n"],"mappings":";;;;;;;AAoCA,eAAsB,iBACpB,KACA,KACA,KACA,UACA,SACA,UACA,gBACe;CACf,MAAM,EAAE,WAAW;AACnB,gBAAe,IAAI;CAEnB,IAAI;AACJ,KAAI;AACF,iBAAe,KAAK,MAAM,IAAI;SACxB;AACN,UAAQ,IAAI;GACV,QAAQ,IAAI,UAAU;GACtB,MAAM,IAAI,OAAO;GACjB,SAAS,eAAe,IAAI,QAAQ;GACpC,MAAM;GACN,UAAU;IAAE,QAAQ;IAAK,SAAS;IAAM;GACzC,CAAC;AACF,qBACE,KACA,KACA,KAAK,UAAU,EACb,OAAO;GACL,SAAS;GACT,MAAM;GACN,MAAM;GACP,EACF,CAAC,CACH;AACD;;CAIF,MAAM,SAAmB,MAAM,QAAQ,aAAa,MAAM,GACtD,aAAa,QACb,CAAC,aAAa,MAAM;CAGxB,MAAM,gBAAgB,OAAO,KAAK,IAAI;CAItC,MAAM,eAAsC;EAC1C,OAAO,aAAa;EACpB,UAAU,EAAE;EACZ,gBAAgB;EAChB,eAAe;EAChB;CAED,MAAM,SAAS,UAAU,IAAI;CAC7B,MAAM,UAAU,aACd,UACA,cACA,QAAQ,6BAA6B,OAAO,EAC5C,SAAS,iBACV;AAED,KAAI,QACF,SAAQ,2BAA2B,SAAS,UAAU,OAAO;AAG/D,KACE,WACE,KACA,SACA,SAAS,OACT,IAAI,SACJ,SACA;EACE,QAAQ,IAAI,UAAU;EACtB,MAAM,IAAI,OAAO;EACjB,SAAS,eAAe,IAAI,QAAQ;EACpC,MAAM;EACP,EACD,SAAS,UACT,SAAS,OACV,CAED;AAEF,KAAI,SAAS;EACX,MAAM,WAAW,QAAQ;AAGzB,MAAI,gBAAgB,SAAS,EAAE;GAC7B,MAAM,SAAS,SAAS,UAAU;AAClC,WAAQ,IAAI;IACV,QAAQ,IAAI,UAAU;IACtB,MAAM,IAAI,OAAO;IACjB,SAAS,eAAe,IAAI,QAAQ;IACpC,MAAM;IACN,UAAU;KAAE;KAAQ;KAAS;IAC9B,CAAC;AACF,sBAAmB,KAAK,QAAQ,KAAK,UAAU,SAAS,CAAC;AACzD;;AAIF,MAAI,oBAAoB,SAAS,EAAE;AACjC,WAAQ,IAAI;IACV,QAAQ,IAAI,UAAU;IACtB,MAAM,IAAI,OAAO;IACjB,SAAS,eAAe,IAAI,QAAQ;IACpC,MAAM;IACN,UAAU;KAAE,QAAQ;KAAK;KAAS;IACnC,CAAC;GAEF,MAAM,OAAO,uBADM,OAAO,UAAU,CAAC,GAAG,SAAS,UAAU,CAAC,EACZ,aAAa,MAAM;AACnE,OAAI,UAAU,KAAK,EAAE,gBAAgB,oBAAoB,CAAC;AAC1D,OAAI,IAAI,KAAK,UAAU,KAAK,CAAC;AAC7B;;AAIF,UAAQ,IAAI;GACV,QAAQ,IAAI,UAAU;GACtB,MAAM,IAAI,OAAO;GACjB,SAAS,eAAe,IAAI,QAAQ;GACpC,MAAM;GACN,UAAU;IAAE,QAAQ;IAAK;IAAS;GACnC,CAAC;AACF,qBACE,KACA,KACA,KAAK,UAAU,EACb,OAAO;GACL,SACE;GACF,MAAM;GACP,EACF,CAAC,CACH;AACD;;AAIF,KAAI,SAAS,QAWX;MAVgB,MAAM,eACpB,KACA,KACA,cACA,UACA,IAAI,OAAO,kBACX,UACA,UACA,IACD,EACY;AACX,WAAQ,IAAI;IACV,QAAQ,IAAI,UAAU;IACtB,MAAM,IAAI,OAAO;IACjB,SAAS,eAAe,IAAI,QAAQ;IACpC,MAAM;IACN,UAAU;KAAE,QAAQ,IAAI,cAAc;KAAK,SAAS;KAAM;IAC3D,CAAC;AACF;;;AAIJ,KAAI,SAAS,QAAQ;AACnB,SAAO,MACL,kCAAkC,IAAI,UAAU,OAAO,GAAG,IAAI,OAAO,mBACtE;AACD,UAAQ,IAAI;GACV,QAAQ,IAAI,UAAU;GACtB,MAAM,IAAI,OAAO;GACjB,SAAS,eAAe,IAAI,QAAQ;GACpC,MAAM;GACN,UAAU;IAAE,QAAQ;IAAK,SAAS;IAAM;GACzC,CAAC;AACF,qBACE,KACA,KACA,KAAK,UAAU,EACb,OAAO;GACL,SAAS;GACT,MAAM;GACN,MAAM;GACP,EACF,CAAC,CACH;AACD;;AAIF,QAAO,KACL,qCAAqC,cAAc,MAAM,GAAG,GAAG,CAAC,sCACjE;CACD,MAAM,aAAa,aAAa,cAAc;CAC9C,MAAM,aAAa,OAAO,KAAK,UAAU,+BAA+B,OAAO,WAAW,CAAC;AAE3F,SAAQ,IAAI;EACV,QAAQ,IAAI,UAAU;EACtB,MAAM,IAAI,OAAO;EACjB,SAAS,eAAe,IAAI,QAAQ;EACpC,MAAM;EACN,UAAU;GAAE,QAAQ;GAAK,SAAS;GAAM;EACzC,CAAC;CAEF,MAAM,OAAO,uBAAuB,YAAY,aAAa,MAAM;AACnE,KAAI,UAAU,KAAK,EAAE,gBAAgB,oBAAoB,CAAC;AAC1D,KAAI,IAAI,KAAK,UAAU,KAAK,CAAC"}
package/dist/gemini.cjs CHANGED
@@ -310,6 +310,7 @@ async function handleGemini(req, res, raw, model, streaming, fixtures, journal,
310
310
  return;
311
311
  }
312
312
  const completionReq = geminiToCompletionRequest(geminiReq, model, streaming);
313
+ completionReq._endpointType = "chat";
313
314
  const testId = require_helpers.getTestId(req);
314
315
  const fixture = require_router.matchFixture(fixtures, completionReq, journal.getFixtureMatchCountsForTest(testId), defaults.requestTransform);
315
316
  const path = req.url ?? `/v1beta/models/${model}:generateContent`;
@@ -1 +1 @@
1
- {"version":3,"file":"gemini.cjs","names":["generateToolCallId","calculateDelay","delay","flattenHeaders","getTestId","matchFixture","applyChaos","proxyAndRecord","isErrorResponse","isContentWithToolCallsResponse","createInterruptionSignal","isTextResponse","isToolCallResponse"],"sources":["../src/gemini.ts"],"sourcesContent":["/**\n * Google Gemini GenerateContent API support.\n *\n * Translates incoming Gemini requests into the ChatCompletionRequest format\n * used by the fixture router, and converts fixture responses back into the\n * Gemini GenerateContent streaming (or non-streaming) format.\n */\n\nimport type * as http from \"node:http\";\nimport type {\n ChatCompletionRequest,\n ChatMessage,\n Fixture,\n HandlerDefaults,\n RecordProviderKey,\n StreamingProfile,\n ToolCall,\n ToolDefinition,\n} from \"./types.js\";\nimport {\n isTextResponse,\n isToolCallResponse,\n isContentWithToolCallsResponse,\n isErrorResponse,\n generateToolCallId,\n flattenHeaders,\n getTestId,\n} from \"./helpers.js\";\nimport { matchFixture } from \"./router.js\";\nimport { writeErrorResponse, delay, calculateDelay } from \"./sse-writer.js\";\nimport { createInterruptionSignal } from \"./interruption.js\";\nimport type { Journal } from \"./journal.js\";\nimport type { Logger } from \"./logger.js\";\nimport { applyChaos } from \"./chaos.js\";\nimport { proxyAndRecord } from \"./recorder.js\";\n\n// ─── Gemini request types ───────────────────────────────────────────────────\n\ninterface GeminiPart {\n text?: string;\n thought?: boolean;\n functionCall?: { name: string; args: Record<string, unknown>; id?: string };\n functionResponse?: { name: string; response: unknown };\n}\n\ninterface GeminiContent {\n role?: string;\n parts: GeminiPart[];\n}\n\ninterface GeminiFunctionDeclaration {\n name: string;\n description?: string;\n parameters?: object;\n}\n\ninterface GeminiToolDef {\n functionDeclarations?: GeminiFunctionDeclaration[];\n}\n\ninterface GeminiRequest {\n contents?: GeminiContent[];\n systemInstruction?: GeminiContent;\n tools?: GeminiToolDef[];\n generationConfig?: {\n temperature?: number;\n maxOutputTokens?: number;\n [key: string]: unknown;\n };\n [key: string]: unknown;\n}\n\n// ─── Input conversion: Gemini → ChatCompletions messages ────────────────────\n\nexport function geminiToCompletionRequest(\n req: GeminiRequest,\n model: string,\n stream: boolean,\n): ChatCompletionRequest {\n const messages: ChatMessage[] = [];\n\n // systemInstruction → system message\n if (req.systemInstruction) {\n const text = req.systemInstruction.parts\n .filter((p) => p.text !== undefined)\n .map((p) => p.text!)\n .join(\"\");\n if (text) {\n messages.push({ role: \"system\", content: text });\n }\n }\n\n if (req.contents) {\n for (const content of req.contents) {\n const role = content.role ?? \"user\";\n\n if (role === \"user\") {\n // Check for functionResponse parts\n const funcResponses = content.parts.filter((p) => p.functionResponse);\n const textParts = content.parts.filter((p) => p.text !== undefined);\n\n if (funcResponses.length > 0) {\n // functionResponse → tool message\n for (let i = 0; i < funcResponses.length; i++) {\n const part = funcResponses[i];\n messages.push({\n role: \"tool\",\n content:\n typeof part.functionResponse!.response === \"string\"\n ? part.functionResponse!.response\n : JSON.stringify(part.functionResponse!.response),\n tool_call_id: `call_gemini_${part.functionResponse!.name}_${i}`,\n });\n }\n // Any text parts alongside → user message\n if (textParts.length > 0) {\n messages.push({\n role: \"user\",\n content: textParts.map((p) => p.text!).join(\"\"),\n });\n }\n } else {\n // Regular user text\n const text = textParts.map((p) => p.text!).join(\"\");\n messages.push({ role: \"user\", content: text });\n }\n } else if (role === \"model\") {\n // Check for functionCall parts\n const funcCalls = content.parts.filter((p) => p.functionCall);\n const textParts = content.parts.filter((p) => p.text !== undefined);\n\n if (funcCalls.length > 0) {\n messages.push({\n role: \"assistant\",\n content: null,\n tool_calls: funcCalls.map((p, i) => ({\n id: `call_gemini_${p.functionCall!.name}_${i}`,\n type: \"function\" as const,\n function: {\n name: p.functionCall!.name,\n arguments: JSON.stringify(p.functionCall!.args),\n },\n })),\n });\n } else {\n const text = textParts.map((p) => p.text!).join(\"\");\n messages.push({ role: \"assistant\", content: text });\n }\n }\n }\n }\n\n // Convert tools\n let tools: ToolDefinition[] | undefined;\n if (req.tools && req.tools.length > 0) {\n const decls = req.tools.flatMap((t) => t.functionDeclarations ?? []);\n if (decls.length > 0) {\n tools = decls.map((d) => ({\n type: \"function\" as const,\n function: {\n name: d.name,\n description: d.description,\n parameters: d.parameters,\n },\n }));\n }\n }\n\n return {\n model,\n messages,\n stream,\n temperature: req.generationConfig?.temperature,\n tools,\n };\n}\n\n// ─── Response building: fixture → Gemini format ─────────────────────────────\n\ninterface GeminiResponseChunk {\n candidates: {\n content: { role: string; parts: GeminiPart[] };\n finishReason?: string;\n index: number;\n }[];\n usageMetadata?: {\n promptTokenCount: number;\n candidatesTokenCount: number;\n totalTokenCount: number;\n };\n}\n\nfunction buildGeminiTextStreamChunks(\n content: string,\n chunkSize: number,\n reasoning?: string,\n): GeminiResponseChunk[] {\n const chunks: GeminiResponseChunk[] = [];\n\n // Reasoning chunks (thought: true)\n if (reasoning) {\n for (let i = 0; i < reasoning.length; i += chunkSize) {\n const slice = reasoning.slice(i, i + chunkSize);\n chunks.push({\n candidates: [\n {\n content: { role: \"model\", parts: [{ text: slice, thought: true }] },\n index: 0,\n },\n ],\n });\n }\n }\n\n // Content chunks (original logic unchanged)\n for (let i = 0; i < content.length; i += chunkSize) {\n const slice = content.slice(i, i + chunkSize);\n const isLast = i + chunkSize >= content.length;\n const chunk: GeminiResponseChunk = {\n candidates: [\n {\n content: { role: \"model\", parts: [{ text: slice }] },\n index: 0,\n ...(isLast ? { finishReason: \"STOP\" } : {}),\n },\n ],\n ...(isLast\n ? {\n usageMetadata: {\n promptTokenCount: 0,\n candidatesTokenCount: 0,\n totalTokenCount: 0,\n },\n }\n : {}),\n };\n chunks.push(chunk);\n }\n\n // Handle empty content (original logic unchanged)\n if (content.length === 0) {\n chunks.push({\n candidates: [\n {\n content: { role: \"model\", parts: [{ text: \"\" }] },\n finishReason: \"STOP\",\n index: 0,\n },\n ],\n usageMetadata: {\n promptTokenCount: 0,\n candidatesTokenCount: 0,\n totalTokenCount: 0,\n },\n });\n }\n\n return chunks;\n}\n\nfunction parseToolCallPart(tc: ToolCall, logger: Logger): GeminiPart {\n let argsObj: Record<string, unknown>;\n try {\n argsObj = JSON.parse(tc.arguments || \"{}\") as Record<string, unknown>;\n } catch {\n logger.warn(`Malformed JSON in fixture tool call arguments for \"${tc.name}\": ${tc.arguments}`);\n argsObj = {};\n }\n return { functionCall: { name: tc.name, args: argsObj, id: tc.id || generateToolCallId() } };\n}\n\nfunction buildGeminiToolCallStreamChunks(\n toolCalls: ToolCall[],\n logger: Logger,\n): GeminiResponseChunk[] {\n const parts: GeminiPart[] = toolCalls.map((tc) => parseToolCallPart(tc, logger));\n\n // Gemini sends all tool calls in a single response chunk\n return [\n {\n candidates: [\n {\n content: { role: \"model\", parts },\n finishReason: \"FUNCTION_CALL\",\n index: 0,\n },\n ],\n usageMetadata: {\n promptTokenCount: 0,\n candidatesTokenCount: 0,\n totalTokenCount: 0,\n },\n },\n ];\n}\n\n// Non-streaming response builders\n\nfunction buildGeminiTextResponse(content: string, reasoning?: string): GeminiResponseChunk {\n const parts: GeminiPart[] = [];\n if (reasoning) {\n parts.push({ text: reasoning, thought: true });\n }\n parts.push({ text: content });\n\n return {\n candidates: [\n {\n content: { role: \"model\", parts },\n finishReason: \"STOP\",\n index: 0,\n },\n ],\n usageMetadata: {\n promptTokenCount: 0,\n candidatesTokenCount: 0,\n totalTokenCount: 0,\n },\n };\n}\n\nfunction buildGeminiToolCallResponse(toolCalls: ToolCall[], logger: Logger): GeminiResponseChunk {\n const parts: GeminiPart[] = toolCalls.map((tc) => parseToolCallPart(tc, logger));\n\n return {\n candidates: [\n {\n content: { role: \"model\", parts },\n finishReason: \"FUNCTION_CALL\",\n index: 0,\n },\n ],\n usageMetadata: {\n promptTokenCount: 0,\n candidatesTokenCount: 0,\n totalTokenCount: 0,\n },\n };\n}\n\nfunction buildGeminiContentWithToolCallsStreamChunks(\n content: string,\n toolCalls: ToolCall[],\n chunkSize: number,\n logger: Logger,\n): GeminiResponseChunk[] {\n const chunks: GeminiResponseChunk[] = [];\n\n if (content.length === 0) {\n chunks.push({\n candidates: [\n {\n content: { role: \"model\", parts: [{ text: \"\" }] },\n index: 0,\n },\n ],\n });\n } else {\n for (let i = 0; i < content.length; i += chunkSize) {\n const slice = content.slice(i, i + chunkSize);\n chunks.push({\n candidates: [\n {\n content: { role: \"model\", parts: [{ text: slice }] },\n index: 0,\n },\n ],\n });\n }\n }\n\n const parts: GeminiPart[] = toolCalls.map((tc) => parseToolCallPart(tc, logger));\n\n chunks.push({\n candidates: [\n {\n content: { role: \"model\", parts },\n finishReason: \"FUNCTION_CALL\",\n index: 0,\n },\n ],\n usageMetadata: {\n promptTokenCount: 0,\n candidatesTokenCount: 0,\n totalTokenCount: 0,\n },\n });\n\n return chunks;\n}\n\nfunction buildGeminiContentWithToolCallsResponse(\n content: string,\n toolCalls: ToolCall[],\n logger: Logger,\n): GeminiResponseChunk {\n const parts: GeminiPart[] = [\n { text: content },\n ...toolCalls.map((tc) => parseToolCallPart(tc, logger)),\n ];\n\n return {\n candidates: [\n {\n content: { role: \"model\", parts },\n finishReason: \"FUNCTION_CALL\",\n index: 0,\n },\n ],\n usageMetadata: {\n promptTokenCount: 0,\n candidatesTokenCount: 0,\n totalTokenCount: 0,\n },\n };\n}\n\n// ─── SSE writer for Gemini streaming ────────────────────────────────────────\n\ninterface GeminiStreamOptions {\n latency?: number;\n streamingProfile?: StreamingProfile;\n signal?: AbortSignal;\n onChunkSent?: () => void;\n}\n\nasync function writeGeminiSSEStream(\n res: http.ServerResponse,\n chunks: GeminiResponseChunk[],\n optionsOrLatency?: number | GeminiStreamOptions,\n): Promise<boolean> {\n const opts: GeminiStreamOptions =\n typeof optionsOrLatency === \"number\" ? { latency: optionsOrLatency } : (optionsOrLatency ?? {});\n const latency = opts.latency ?? 0;\n const profile = opts.streamingProfile;\n const signal = opts.signal;\n const onChunkSent = opts.onChunkSent;\n\n if (res.writableEnded) return true;\n res.setHeader(\"Content-Type\", \"text/event-stream\");\n res.setHeader(\"Cache-Control\", \"no-cache\");\n res.setHeader(\"Connection\", \"keep-alive\");\n\n let chunkIndex = 0;\n for (const chunk of chunks) {\n const chunkDelay = calculateDelay(chunkIndex, profile, latency);\n if (chunkDelay > 0) await delay(chunkDelay, signal);\n if (signal?.aborted) return false;\n if (res.writableEnded) return true;\n // Gemini uses data-only SSE (no event: prefix, no [DONE])\n res.write(`data: ${JSON.stringify(chunk)}\\n\\n`);\n onChunkSent?.();\n if (signal?.aborted) return false;\n chunkIndex++;\n }\n\n if (!res.writableEnded) {\n res.end();\n }\n return true;\n}\n\n// ─── Request handler ────────────────────────────────────────────────────────\n\nexport async function handleGemini(\n req: http.IncomingMessage,\n res: http.ServerResponse,\n raw: string,\n model: string,\n streaming: boolean,\n fixtures: Fixture[],\n journal: Journal,\n defaults: HandlerDefaults,\n setCorsHeaders: (res: http.ServerResponse) => void,\n providerKey: RecordProviderKey = \"gemini\",\n): Promise<void> {\n const { logger } = defaults;\n setCorsHeaders(res);\n\n let geminiReq: GeminiRequest;\n try {\n geminiReq = JSON.parse(raw) as GeminiRequest;\n } catch {\n journal.add({\n method: req.method ?? \"POST\",\n path: req.url ?? `/v1beta/models/${model}:generateContent`,\n headers: flattenHeaders(req.headers),\n body: null,\n response: { status: 400, fixture: null },\n });\n writeErrorResponse(\n res,\n 400,\n JSON.stringify({\n error: {\n message: \"Malformed JSON\",\n code: 400,\n status: \"INVALID_ARGUMENT\",\n },\n }),\n );\n return;\n }\n\n // Convert to ChatCompletionRequest for fixture matching\n const completionReq = geminiToCompletionRequest(geminiReq, model, streaming);\n\n const testId = getTestId(req);\n const fixture = matchFixture(\n fixtures,\n completionReq,\n journal.getFixtureMatchCountsForTest(testId),\n defaults.requestTransform,\n );\n const path = req.url ?? `/v1beta/models/${model}:generateContent`;\n\n if (fixture) {\n journal.incrementFixtureMatchCount(fixture, fixtures, testId);\n }\n\n if (\n applyChaos(\n res,\n fixture,\n defaults.chaos,\n req.headers,\n journal,\n {\n method: req.method ?? \"POST\",\n path,\n headers: flattenHeaders(req.headers),\n body: completionReq,\n },\n defaults.registry,\n defaults.logger,\n )\n )\n return;\n\n if (!fixture) {\n if (defaults.record) {\n const proxied = await proxyAndRecord(\n req,\n res,\n completionReq,\n providerKey,\n path,\n fixtures,\n defaults,\n raw,\n );\n if (proxied) {\n journal.add({\n method: req.method ?? \"POST\",\n path,\n headers: flattenHeaders(req.headers),\n body: completionReq,\n response: { status: res.statusCode ?? 200, fixture: null },\n });\n return;\n }\n }\n const strictStatus = defaults.strict ? 503 : 404;\n const strictMessage = defaults.strict\n ? \"Strict mode: no fixture matched\"\n : \"No fixture matched\";\n if (defaults.strict) {\n logger.error(`STRICT: No fixture matched for ${req.method ?? \"POST\"} ${path}`);\n }\n journal.add({\n method: req.method ?? \"POST\",\n path,\n headers: flattenHeaders(req.headers),\n body: completionReq,\n response: { status: strictStatus, fixture: null },\n });\n writeErrorResponse(\n res,\n strictStatus,\n JSON.stringify({\n error: {\n message: strictMessage,\n code: strictStatus,\n status: defaults.strict ? \"UNAVAILABLE\" : \"NOT_FOUND\",\n },\n }),\n );\n return;\n }\n\n const response = fixture.response;\n const latency = fixture.latency ?? defaults.latency;\n const chunkSize = Math.max(1, fixture.chunkSize ?? defaults.chunkSize);\n\n // Error response\n if (isErrorResponse(response)) {\n const status = response.status ?? 500;\n journal.add({\n method: req.method ?? \"POST\",\n path,\n headers: flattenHeaders(req.headers),\n body: completionReq,\n response: { status, fixture },\n });\n // Gemini-style error format: { error: { code, message, status } }\n const geminiError = {\n error: {\n code: status,\n message: response.error.message,\n status: response.error.type ?? \"ERROR\",\n },\n };\n writeErrorResponse(res, status, JSON.stringify(geminiError));\n return;\n }\n\n // Content + tool calls response (must be checked before isTextResponse / isToolCallResponse)\n if (isContentWithToolCallsResponse(response)) {\n const journalEntry = journal.add({\n method: req.method ?? \"POST\",\n path,\n headers: flattenHeaders(req.headers),\n body: completionReq,\n response: { status: 200, fixture },\n });\n if (!streaming) {\n const body = buildGeminiContentWithToolCallsResponse(\n response.content,\n response.toolCalls,\n logger,\n );\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify(body));\n } else {\n const chunks = buildGeminiContentWithToolCallsStreamChunks(\n response.content,\n response.toolCalls,\n chunkSize,\n logger,\n );\n const interruption = createInterruptionSignal(fixture);\n const completed = await writeGeminiSSEStream(res, chunks, {\n latency,\n streamingProfile: fixture.streamingProfile,\n signal: interruption?.signal,\n onChunkSent: interruption?.tick,\n });\n if (!completed) {\n if (!res.writableEnded) res.destroy();\n journalEntry.response.interrupted = true;\n journalEntry.response.interruptReason = interruption?.reason();\n }\n interruption?.cleanup();\n }\n return;\n }\n\n // Text response\n if (isTextResponse(response)) {\n const journalEntry = journal.add({\n method: req.method ?? \"POST\",\n path,\n headers: flattenHeaders(req.headers),\n body: completionReq,\n response: { status: 200, fixture },\n });\n if (!streaming) {\n const body = buildGeminiTextResponse(response.content, response.reasoning);\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify(body));\n } else {\n const chunks = buildGeminiTextStreamChunks(response.content, chunkSize, response.reasoning);\n const interruption = createInterruptionSignal(fixture);\n const completed = await writeGeminiSSEStream(res, chunks, {\n latency,\n streamingProfile: fixture.streamingProfile,\n signal: interruption?.signal,\n onChunkSent: interruption?.tick,\n });\n if (!completed) {\n if (!res.writableEnded) res.destroy();\n journalEntry.response.interrupted = true;\n journalEntry.response.interruptReason = interruption?.reason();\n }\n interruption?.cleanup();\n }\n return;\n }\n\n // Tool call response\n if (isToolCallResponse(response)) {\n const journalEntry = journal.add({\n method: req.method ?? \"POST\",\n path,\n headers: flattenHeaders(req.headers),\n body: completionReq,\n response: { status: 200, fixture },\n });\n if (!streaming) {\n const body = buildGeminiToolCallResponse(response.toolCalls, logger);\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify(body));\n } else {\n const chunks = buildGeminiToolCallStreamChunks(response.toolCalls, logger);\n const interruption = createInterruptionSignal(fixture);\n const completed = await writeGeminiSSEStream(res, chunks, {\n latency,\n streamingProfile: fixture.streamingProfile,\n signal: interruption?.signal,\n onChunkSent: interruption?.tick,\n });\n if (!completed) {\n if (!res.writableEnded) res.destroy();\n journalEntry.response.interrupted = true;\n journalEntry.response.interruptReason = interruption?.reason();\n }\n interruption?.cleanup();\n }\n return;\n }\n\n // Unknown response type\n journal.add({\n method: req.method ?? \"POST\",\n path,\n headers: flattenHeaders(req.headers),\n body: completionReq,\n response: { status: 500, fixture },\n });\n writeErrorResponse(\n res,\n 500,\n JSON.stringify({\n error: {\n message: \"Fixture response did not match any known type\",\n code: 500,\n status: \"INTERNAL\",\n },\n }),\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"gemini.cjs","names":["generateToolCallId","calculateDelay","delay","flattenHeaders","getTestId","matchFixture","applyChaos","proxyAndRecord","isErrorResponse","isContentWithToolCallsResponse","createInterruptionSignal","isTextResponse","isToolCallResponse"],"sources":["../src/gemini.ts"],"sourcesContent":["/**\n * Google Gemini GenerateContent API support.\n *\n * Translates incoming Gemini requests into the ChatCompletionRequest format\n * used by the fixture router, and converts fixture responses back into the\n * Gemini GenerateContent streaming (or non-streaming) format.\n */\n\nimport type * as http from \"node:http\";\nimport type {\n ChatCompletionRequest,\n ChatMessage,\n Fixture,\n HandlerDefaults,\n RecordProviderKey,\n StreamingProfile,\n ToolCall,\n ToolDefinition,\n} from \"./types.js\";\nimport {\n isTextResponse,\n isToolCallResponse,\n isContentWithToolCallsResponse,\n isErrorResponse,\n generateToolCallId,\n flattenHeaders,\n getTestId,\n} from \"./helpers.js\";\nimport { matchFixture } from \"./router.js\";\nimport { writeErrorResponse, delay, calculateDelay } from \"./sse-writer.js\";\nimport { createInterruptionSignal } from \"./interruption.js\";\nimport type { Journal } from \"./journal.js\";\nimport type { Logger } from \"./logger.js\";\nimport { applyChaos } from \"./chaos.js\";\nimport { proxyAndRecord } from \"./recorder.js\";\n\n// ─── Gemini request types ───────────────────────────────────────────────────\n\ninterface GeminiPart {\n text?: string;\n thought?: boolean;\n functionCall?: { name: string; args: Record<string, unknown>; id?: string };\n functionResponse?: { name: string; response: unknown };\n}\n\ninterface GeminiContent {\n role?: string;\n parts: GeminiPart[];\n}\n\ninterface GeminiFunctionDeclaration {\n name: string;\n description?: string;\n parameters?: object;\n}\n\ninterface GeminiToolDef {\n functionDeclarations?: GeminiFunctionDeclaration[];\n}\n\ninterface GeminiRequest {\n contents?: GeminiContent[];\n systemInstruction?: GeminiContent;\n tools?: GeminiToolDef[];\n generationConfig?: {\n temperature?: number;\n maxOutputTokens?: number;\n [key: string]: unknown;\n };\n [key: string]: unknown;\n}\n\n// ─── Input conversion: Gemini → ChatCompletions messages ────────────────────\n\nexport function geminiToCompletionRequest(\n req: GeminiRequest,\n model: string,\n stream: boolean,\n): ChatCompletionRequest {\n const messages: ChatMessage[] = [];\n\n // systemInstruction → system message\n if (req.systemInstruction) {\n const text = req.systemInstruction.parts\n .filter((p) => p.text !== undefined)\n .map((p) => p.text!)\n .join(\"\");\n if (text) {\n messages.push({ role: \"system\", content: text });\n }\n }\n\n if (req.contents) {\n for (const content of req.contents) {\n const role = content.role ?? \"user\";\n\n if (role === \"user\") {\n // Check for functionResponse parts\n const funcResponses = content.parts.filter((p) => p.functionResponse);\n const textParts = content.parts.filter((p) => p.text !== undefined);\n\n if (funcResponses.length > 0) {\n // functionResponse → tool message\n for (let i = 0; i < funcResponses.length; i++) {\n const part = funcResponses[i];\n messages.push({\n role: \"tool\",\n content:\n typeof part.functionResponse!.response === \"string\"\n ? part.functionResponse!.response\n : JSON.stringify(part.functionResponse!.response),\n tool_call_id: `call_gemini_${part.functionResponse!.name}_${i}`,\n });\n }\n // Any text parts alongside → user message\n if (textParts.length > 0) {\n messages.push({\n role: \"user\",\n content: textParts.map((p) => p.text!).join(\"\"),\n });\n }\n } else {\n // Regular user text\n const text = textParts.map((p) => p.text!).join(\"\");\n messages.push({ role: \"user\", content: text });\n }\n } else if (role === \"model\") {\n // Check for functionCall parts\n const funcCalls = content.parts.filter((p) => p.functionCall);\n const textParts = content.parts.filter((p) => p.text !== undefined);\n\n if (funcCalls.length > 0) {\n messages.push({\n role: \"assistant\",\n content: null,\n tool_calls: funcCalls.map((p, i) => ({\n id: `call_gemini_${p.functionCall!.name}_${i}`,\n type: \"function\" as const,\n function: {\n name: p.functionCall!.name,\n arguments: JSON.stringify(p.functionCall!.args),\n },\n })),\n });\n } else {\n const text = textParts.map((p) => p.text!).join(\"\");\n messages.push({ role: \"assistant\", content: text });\n }\n }\n }\n }\n\n // Convert tools\n let tools: ToolDefinition[] | undefined;\n if (req.tools && req.tools.length > 0) {\n const decls = req.tools.flatMap((t) => t.functionDeclarations ?? []);\n if (decls.length > 0) {\n tools = decls.map((d) => ({\n type: \"function\" as const,\n function: {\n name: d.name,\n description: d.description,\n parameters: d.parameters,\n },\n }));\n }\n }\n\n return {\n model,\n messages,\n stream,\n temperature: req.generationConfig?.temperature,\n tools,\n };\n}\n\n// ─── Response building: fixture → Gemini format ─────────────────────────────\n\ninterface GeminiResponseChunk {\n candidates: {\n content: { role: string; parts: GeminiPart[] };\n finishReason?: string;\n index: number;\n }[];\n usageMetadata?: {\n promptTokenCount: number;\n candidatesTokenCount: number;\n totalTokenCount: number;\n };\n}\n\nfunction buildGeminiTextStreamChunks(\n content: string,\n chunkSize: number,\n reasoning?: string,\n): GeminiResponseChunk[] {\n const chunks: GeminiResponseChunk[] = [];\n\n // Reasoning chunks (thought: true)\n if (reasoning) {\n for (let i = 0; i < reasoning.length; i += chunkSize) {\n const slice = reasoning.slice(i, i + chunkSize);\n chunks.push({\n candidates: [\n {\n content: { role: \"model\", parts: [{ text: slice, thought: true }] },\n index: 0,\n },\n ],\n });\n }\n }\n\n // Content chunks (original logic unchanged)\n for (let i = 0; i < content.length; i += chunkSize) {\n const slice = content.slice(i, i + chunkSize);\n const isLast = i + chunkSize >= content.length;\n const chunk: GeminiResponseChunk = {\n candidates: [\n {\n content: { role: \"model\", parts: [{ text: slice }] },\n index: 0,\n ...(isLast ? { finishReason: \"STOP\" } : {}),\n },\n ],\n ...(isLast\n ? {\n usageMetadata: {\n promptTokenCount: 0,\n candidatesTokenCount: 0,\n totalTokenCount: 0,\n },\n }\n : {}),\n };\n chunks.push(chunk);\n }\n\n // Handle empty content (original logic unchanged)\n if (content.length === 0) {\n chunks.push({\n candidates: [\n {\n content: { role: \"model\", parts: [{ text: \"\" }] },\n finishReason: \"STOP\",\n index: 0,\n },\n ],\n usageMetadata: {\n promptTokenCount: 0,\n candidatesTokenCount: 0,\n totalTokenCount: 0,\n },\n });\n }\n\n return chunks;\n}\n\nfunction parseToolCallPart(tc: ToolCall, logger: Logger): GeminiPart {\n let argsObj: Record<string, unknown>;\n try {\n argsObj = JSON.parse(tc.arguments || \"{}\") as Record<string, unknown>;\n } catch {\n logger.warn(`Malformed JSON in fixture tool call arguments for \"${tc.name}\": ${tc.arguments}`);\n argsObj = {};\n }\n return { functionCall: { name: tc.name, args: argsObj, id: tc.id || generateToolCallId() } };\n}\n\nfunction buildGeminiToolCallStreamChunks(\n toolCalls: ToolCall[],\n logger: Logger,\n): GeminiResponseChunk[] {\n const parts: GeminiPart[] = toolCalls.map((tc) => parseToolCallPart(tc, logger));\n\n // Gemini sends all tool calls in a single response chunk\n return [\n {\n candidates: [\n {\n content: { role: \"model\", parts },\n finishReason: \"FUNCTION_CALL\",\n index: 0,\n },\n ],\n usageMetadata: {\n promptTokenCount: 0,\n candidatesTokenCount: 0,\n totalTokenCount: 0,\n },\n },\n ];\n}\n\n// Non-streaming response builders\n\nfunction buildGeminiTextResponse(content: string, reasoning?: string): GeminiResponseChunk {\n const parts: GeminiPart[] = [];\n if (reasoning) {\n parts.push({ text: reasoning, thought: true });\n }\n parts.push({ text: content });\n\n return {\n candidates: [\n {\n content: { role: \"model\", parts },\n finishReason: \"STOP\",\n index: 0,\n },\n ],\n usageMetadata: {\n promptTokenCount: 0,\n candidatesTokenCount: 0,\n totalTokenCount: 0,\n },\n };\n}\n\nfunction buildGeminiToolCallResponse(toolCalls: ToolCall[], logger: Logger): GeminiResponseChunk {\n const parts: GeminiPart[] = toolCalls.map((tc) => parseToolCallPart(tc, logger));\n\n return {\n candidates: [\n {\n content: { role: \"model\", parts },\n finishReason: \"FUNCTION_CALL\",\n index: 0,\n },\n ],\n usageMetadata: {\n promptTokenCount: 0,\n candidatesTokenCount: 0,\n totalTokenCount: 0,\n },\n };\n}\n\nfunction buildGeminiContentWithToolCallsStreamChunks(\n content: string,\n toolCalls: ToolCall[],\n chunkSize: number,\n logger: Logger,\n): GeminiResponseChunk[] {\n const chunks: GeminiResponseChunk[] = [];\n\n if (content.length === 0) {\n chunks.push({\n candidates: [\n {\n content: { role: \"model\", parts: [{ text: \"\" }] },\n index: 0,\n },\n ],\n });\n } else {\n for (let i = 0; i < content.length; i += chunkSize) {\n const slice = content.slice(i, i + chunkSize);\n chunks.push({\n candidates: [\n {\n content: { role: \"model\", parts: [{ text: slice }] },\n index: 0,\n },\n ],\n });\n }\n }\n\n const parts: GeminiPart[] = toolCalls.map((tc) => parseToolCallPart(tc, logger));\n\n chunks.push({\n candidates: [\n {\n content: { role: \"model\", parts },\n finishReason: \"FUNCTION_CALL\",\n index: 0,\n },\n ],\n usageMetadata: {\n promptTokenCount: 0,\n candidatesTokenCount: 0,\n totalTokenCount: 0,\n },\n });\n\n return chunks;\n}\n\nfunction buildGeminiContentWithToolCallsResponse(\n content: string,\n toolCalls: ToolCall[],\n logger: Logger,\n): GeminiResponseChunk {\n const parts: GeminiPart[] = [\n { text: content },\n ...toolCalls.map((tc) => parseToolCallPart(tc, logger)),\n ];\n\n return {\n candidates: [\n {\n content: { role: \"model\", parts },\n finishReason: \"FUNCTION_CALL\",\n index: 0,\n },\n ],\n usageMetadata: {\n promptTokenCount: 0,\n candidatesTokenCount: 0,\n totalTokenCount: 0,\n },\n };\n}\n\n// ─── SSE writer for Gemini streaming ────────────────────────────────────────\n\ninterface GeminiStreamOptions {\n latency?: number;\n streamingProfile?: StreamingProfile;\n signal?: AbortSignal;\n onChunkSent?: () => void;\n}\n\nasync function writeGeminiSSEStream(\n res: http.ServerResponse,\n chunks: GeminiResponseChunk[],\n optionsOrLatency?: number | GeminiStreamOptions,\n): Promise<boolean> {\n const opts: GeminiStreamOptions =\n typeof optionsOrLatency === \"number\" ? { latency: optionsOrLatency } : (optionsOrLatency ?? {});\n const latency = opts.latency ?? 0;\n const profile = opts.streamingProfile;\n const signal = opts.signal;\n const onChunkSent = opts.onChunkSent;\n\n if (res.writableEnded) return true;\n res.setHeader(\"Content-Type\", \"text/event-stream\");\n res.setHeader(\"Cache-Control\", \"no-cache\");\n res.setHeader(\"Connection\", \"keep-alive\");\n\n let chunkIndex = 0;\n for (const chunk of chunks) {\n const chunkDelay = calculateDelay(chunkIndex, profile, latency);\n if (chunkDelay > 0) await delay(chunkDelay, signal);\n if (signal?.aborted) return false;\n if (res.writableEnded) return true;\n // Gemini uses data-only SSE (no event: prefix, no [DONE])\n res.write(`data: ${JSON.stringify(chunk)}\\n\\n`);\n onChunkSent?.();\n if (signal?.aborted) return false;\n chunkIndex++;\n }\n\n if (!res.writableEnded) {\n res.end();\n }\n return true;\n}\n\n// ─── Request handler ────────────────────────────────────────────────────────\n\nexport async function handleGemini(\n req: http.IncomingMessage,\n res: http.ServerResponse,\n raw: string,\n model: string,\n streaming: boolean,\n fixtures: Fixture[],\n journal: Journal,\n defaults: HandlerDefaults,\n setCorsHeaders: (res: http.ServerResponse) => void,\n providerKey: RecordProviderKey = \"gemini\",\n): Promise<void> {\n const { logger } = defaults;\n setCorsHeaders(res);\n\n let geminiReq: GeminiRequest;\n try {\n geminiReq = JSON.parse(raw) as GeminiRequest;\n } catch {\n journal.add({\n method: req.method ?? \"POST\",\n path: req.url ?? `/v1beta/models/${model}:generateContent`,\n headers: flattenHeaders(req.headers),\n body: null,\n response: { status: 400, fixture: null },\n });\n writeErrorResponse(\n res,\n 400,\n JSON.stringify({\n error: {\n message: \"Malformed JSON\",\n code: 400,\n status: \"INVALID_ARGUMENT\",\n },\n }),\n );\n return;\n }\n\n // Convert to ChatCompletionRequest for fixture matching\n const completionReq = geminiToCompletionRequest(geminiReq, model, streaming);\n completionReq._endpointType = \"chat\";\n\n const testId = getTestId(req);\n const fixture = matchFixture(\n fixtures,\n completionReq,\n journal.getFixtureMatchCountsForTest(testId),\n defaults.requestTransform,\n );\n const path = req.url ?? `/v1beta/models/${model}:generateContent`;\n\n if (fixture) {\n journal.incrementFixtureMatchCount(fixture, fixtures, testId);\n }\n\n if (\n applyChaos(\n res,\n fixture,\n defaults.chaos,\n req.headers,\n journal,\n {\n method: req.method ?? \"POST\",\n path,\n headers: flattenHeaders(req.headers),\n body: completionReq,\n },\n defaults.registry,\n defaults.logger,\n )\n )\n return;\n\n if (!fixture) {\n if (defaults.record) {\n const proxied = await proxyAndRecord(\n req,\n res,\n completionReq,\n providerKey,\n path,\n fixtures,\n defaults,\n raw,\n );\n if (proxied) {\n journal.add({\n method: req.method ?? \"POST\",\n path,\n headers: flattenHeaders(req.headers),\n body: completionReq,\n response: { status: res.statusCode ?? 200, fixture: null },\n });\n return;\n }\n }\n const strictStatus = defaults.strict ? 503 : 404;\n const strictMessage = defaults.strict\n ? \"Strict mode: no fixture matched\"\n : \"No fixture matched\";\n if (defaults.strict) {\n logger.error(`STRICT: No fixture matched for ${req.method ?? \"POST\"} ${path}`);\n }\n journal.add({\n method: req.method ?? \"POST\",\n path,\n headers: flattenHeaders(req.headers),\n body: completionReq,\n response: { status: strictStatus, fixture: null },\n });\n writeErrorResponse(\n res,\n strictStatus,\n JSON.stringify({\n error: {\n message: strictMessage,\n code: strictStatus,\n status: defaults.strict ? \"UNAVAILABLE\" : \"NOT_FOUND\",\n },\n }),\n );\n return;\n }\n\n const response = fixture.response;\n const latency = fixture.latency ?? defaults.latency;\n const chunkSize = Math.max(1, fixture.chunkSize ?? defaults.chunkSize);\n\n // Error response\n if (isErrorResponse(response)) {\n const status = response.status ?? 500;\n journal.add({\n method: req.method ?? \"POST\",\n path,\n headers: flattenHeaders(req.headers),\n body: completionReq,\n response: { status, fixture },\n });\n // Gemini-style error format: { error: { code, message, status } }\n const geminiError = {\n error: {\n code: status,\n message: response.error.message,\n status: response.error.type ?? \"ERROR\",\n },\n };\n writeErrorResponse(res, status, JSON.stringify(geminiError));\n return;\n }\n\n // Content + tool calls response (must be checked before isTextResponse / isToolCallResponse)\n if (isContentWithToolCallsResponse(response)) {\n const journalEntry = journal.add({\n method: req.method ?? \"POST\",\n path,\n headers: flattenHeaders(req.headers),\n body: completionReq,\n response: { status: 200, fixture },\n });\n if (!streaming) {\n const body = buildGeminiContentWithToolCallsResponse(\n response.content,\n response.toolCalls,\n logger,\n );\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify(body));\n } else {\n const chunks = buildGeminiContentWithToolCallsStreamChunks(\n response.content,\n response.toolCalls,\n chunkSize,\n logger,\n );\n const interruption = createInterruptionSignal(fixture);\n const completed = await writeGeminiSSEStream(res, chunks, {\n latency,\n streamingProfile: fixture.streamingProfile,\n signal: interruption?.signal,\n onChunkSent: interruption?.tick,\n });\n if (!completed) {\n if (!res.writableEnded) res.destroy();\n journalEntry.response.interrupted = true;\n journalEntry.response.interruptReason = interruption?.reason();\n }\n interruption?.cleanup();\n }\n return;\n }\n\n // Text response\n if (isTextResponse(response)) {\n const journalEntry = journal.add({\n method: req.method ?? \"POST\",\n path,\n headers: flattenHeaders(req.headers),\n body: completionReq,\n response: { status: 200, fixture },\n });\n if (!streaming) {\n const body = buildGeminiTextResponse(response.content, response.reasoning);\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify(body));\n } else {\n const chunks = buildGeminiTextStreamChunks(response.content, chunkSize, response.reasoning);\n const interruption = createInterruptionSignal(fixture);\n const completed = await writeGeminiSSEStream(res, chunks, {\n latency,\n streamingProfile: fixture.streamingProfile,\n signal: interruption?.signal,\n onChunkSent: interruption?.tick,\n });\n if (!completed) {\n if (!res.writableEnded) res.destroy();\n journalEntry.response.interrupted = true;\n journalEntry.response.interruptReason = interruption?.reason();\n }\n interruption?.cleanup();\n }\n return;\n }\n\n // Tool call response\n if (isToolCallResponse(response)) {\n const journalEntry = journal.add({\n method: req.method ?? \"POST\",\n path,\n headers: flattenHeaders(req.headers),\n body: completionReq,\n response: { status: 200, fixture },\n });\n if (!streaming) {\n const body = buildGeminiToolCallResponse(response.toolCalls, logger);\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify(body));\n } else {\n const chunks = buildGeminiToolCallStreamChunks(response.toolCalls, logger);\n const interruption = createInterruptionSignal(fixture);\n const completed = await writeGeminiSSEStream(res, chunks, {\n latency,\n streamingProfile: fixture.streamingProfile,\n signal: interruption?.signal,\n onChunkSent: interruption?.tick,\n });\n if (!completed) {\n if (!res.writableEnded) res.destroy();\n journalEntry.response.interrupted = true;\n journalEntry.response.interruptReason = interruption?.reason();\n }\n interruption?.cleanup();\n }\n return;\n }\n\n // Unknown response type\n journal.add({\n method: req.method ?? \"POST\",\n path,\n headers: flattenHeaders(req.headers),\n body: completionReq,\n response: { status: 500, fixture },\n });\n writeErrorResponse(\n res,\n 500,\n JSON.stringify({\n error: {\n message: \"Fixture response did not match any known type\",\n code: 500,\n status: \"INTERNAL\",\n },\n }),\n );\n}\n"],"mappings":"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"}
package/dist/gemini.js CHANGED
@@ -310,6 +310,7 @@ async function handleGemini(req, res, raw, model, streaming, fixtures, journal,
310
310
  return;
311
311
  }
312
312
  const completionReq = geminiToCompletionRequest(geminiReq, model, streaming);
313
+ completionReq._endpointType = "chat";
313
314
  const testId = getTestId(req);
314
315
  const fixture = matchFixture(fixtures, completionReq, journal.getFixtureMatchCountsForTest(testId), defaults.requestTransform);
315
316
  const path = req.url ?? `/v1beta/models/${model}:generateContent`;