@copilotkit/aimock 1.11.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 (117) hide show
  1. package/README.md +4 -2
  2. package/dist/a2a-types.d.cts.map +1 -1
  3. package/dist/a2a-types.d.ts.map +1 -1
  4. package/dist/agui-types.d.cts.map +1 -1
  5. package/dist/bedrock-converse.cjs +2 -0
  6. package/dist/bedrock-converse.cjs.map +1 -1
  7. package/dist/bedrock-converse.d.cts.map +1 -1
  8. package/dist/bedrock-converse.d.ts.map +1 -1
  9. package/dist/bedrock-converse.js +2 -0
  10. package/dist/bedrock-converse.js.map +1 -1
  11. package/dist/bedrock.cjs +2 -0
  12. package/dist/bedrock.cjs.map +1 -1
  13. package/dist/bedrock.d.cts.map +1 -1
  14. package/dist/bedrock.d.ts.map +1 -1
  15. package/dist/bedrock.js +2 -0
  16. package/dist/bedrock.js.map +1 -1
  17. package/dist/cohere.cjs +1 -0
  18. package/dist/cohere.cjs.map +1 -1
  19. package/dist/cohere.js +1 -0
  20. package/dist/cohere.js.map +1 -1
  21. package/dist/embeddings.cjs +2 -1
  22. package/dist/embeddings.cjs.map +1 -1
  23. package/dist/embeddings.js +2 -1
  24. package/dist/embeddings.js.map +1 -1
  25. package/dist/gemini.cjs +1 -0
  26. package/dist/gemini.cjs.map +1 -1
  27. package/dist/gemini.js +1 -0
  28. package/dist/gemini.js.map +1 -1
  29. package/dist/helpers.cjs +16 -0
  30. package/dist/helpers.cjs.map +1 -1
  31. package/dist/helpers.d.cts +6 -2
  32. package/dist/helpers.d.cts.map +1 -1
  33. package/dist/helpers.d.ts +6 -2
  34. package/dist/helpers.d.ts.map +1 -1
  35. package/dist/helpers.js +13 -1
  36. package/dist/helpers.js.map +1 -1
  37. package/dist/images.cjs +166 -0
  38. package/dist/images.cjs.map +1 -0
  39. package/dist/images.d.cts +11 -0
  40. package/dist/images.d.cts.map +1 -0
  41. package/dist/images.d.ts +11 -0
  42. package/dist/images.d.ts.map +1 -0
  43. package/dist/images.js +166 -0
  44. package/dist/images.js.map +1 -0
  45. package/dist/index.cjs +13 -0
  46. package/dist/index.d.cts +7 -3
  47. package/dist/index.d.ts +7 -3
  48. package/dist/index.js +6 -2
  49. package/dist/llmock.cjs +37 -1
  50. package/dist/llmock.cjs.map +1 -1
  51. package/dist/llmock.d.cts +5 -1
  52. package/dist/llmock.d.cts.map +1 -1
  53. package/dist/llmock.d.ts +5 -1
  54. package/dist/llmock.d.ts.map +1 -1
  55. package/dist/llmock.js +37 -1
  56. package/dist/llmock.js.map +1 -1
  57. package/dist/messages.cjs +1 -0
  58. package/dist/messages.cjs.map +1 -1
  59. package/dist/messages.js +1 -0
  60. package/dist/messages.js.map +1 -1
  61. package/dist/ollama.cjs +2 -0
  62. package/dist/ollama.cjs.map +1 -1
  63. package/dist/ollama.d.cts.map +1 -1
  64. package/dist/ollama.d.ts.map +1 -1
  65. package/dist/ollama.js +2 -0
  66. package/dist/ollama.js.map +1 -1
  67. package/dist/recorder.cjs +50 -7
  68. package/dist/recorder.cjs.map +1 -1
  69. package/dist/recorder.js +50 -7
  70. package/dist/recorder.js.map +1 -1
  71. package/dist/responses.cjs +1 -0
  72. package/dist/responses.cjs.map +1 -1
  73. package/dist/responses.js +1 -0
  74. package/dist/responses.js.map +1 -1
  75. package/dist/router.cjs +8 -0
  76. package/dist/router.cjs.map +1 -1
  77. package/dist/router.d.cts.map +1 -1
  78. package/dist/router.d.ts.map +1 -1
  79. package/dist/router.js +9 -0
  80. package/dist/router.js.map +1 -1
  81. package/dist/server.cjs +80 -3
  82. package/dist/server.cjs.map +1 -1
  83. package/dist/server.d.cts +2 -0
  84. package/dist/server.d.cts.map +1 -1
  85. package/dist/server.d.ts +2 -0
  86. package/dist/server.d.ts.map +1 -1
  87. package/dist/server.js +80 -3
  88. package/dist/server.js.map +1 -1
  89. package/dist/speech.cjs +144 -0
  90. package/dist/speech.cjs.map +1 -0
  91. package/dist/speech.d.cts +11 -0
  92. package/dist/speech.d.cts.map +1 -0
  93. package/dist/speech.d.ts +11 -0
  94. package/dist/speech.d.ts.map +1 -0
  95. package/dist/speech.js +144 -0
  96. package/dist/speech.js.map +1 -0
  97. package/dist/transcription.cjs +134 -0
  98. package/dist/transcription.cjs.map +1 -0
  99. package/dist/transcription.d.cts +11 -0
  100. package/dist/transcription.d.cts.map +1 -0
  101. package/dist/transcription.d.ts +11 -0
  102. package/dist/transcription.d.ts.map +1 -0
  103. package/dist/transcription.js +134 -0
  104. package/dist/transcription.js.map +1 -0
  105. package/dist/types.d.cts +44 -2
  106. package/dist/types.d.cts.map +1 -1
  107. package/dist/types.d.ts +44 -2
  108. package/dist/types.d.ts.map +1 -1
  109. package/dist/video.cjs +196 -0
  110. package/dist/video.cjs.map +1 -0
  111. package/dist/video.d.cts +14 -0
  112. package/dist/video.d.cts.map +1 -0
  113. package/dist/video.d.ts +14 -0
  114. package/dist/video.d.ts.map +1 -0
  115. package/dist/video.js +195 -0
  116. package/dist/video.js.map +1 -0
  117. package/package.json +2 -2
@@ -0,0 +1,144 @@
1
+ const require_helpers = require('./helpers.cjs');
2
+ const require_router = require('./router.cjs');
3
+ const require_sse_writer = require('./sse-writer.cjs');
4
+ const require_chaos = require('./chaos.cjs');
5
+ const require_recorder = require('./recorder.cjs');
6
+
7
+ //#region src/speech.ts
8
+ const FORMAT_TO_CONTENT_TYPE = {
9
+ mp3: "audio/mpeg",
10
+ opus: "audio/opus",
11
+ aac: "audio/aac",
12
+ flac: "audio/flac",
13
+ wav: "audio/wav",
14
+ pcm: "audio/pcm"
15
+ };
16
+ async function handleSpeech(req, res, raw, fixtures, journal, defaults, setCorsHeaders) {
17
+ setCorsHeaders(res);
18
+ const path = req.url ?? "/v1/audio/speech";
19
+ const method = req.method ?? "POST";
20
+ let speechReq;
21
+ try {
22
+ speechReq = JSON.parse(raw);
23
+ } catch {
24
+ journal.add({
25
+ method,
26
+ path,
27
+ headers: require_helpers.flattenHeaders(req.headers),
28
+ body: null,
29
+ response: {
30
+ status: 400,
31
+ fixture: null
32
+ }
33
+ });
34
+ require_sse_writer.writeErrorResponse(res, 400, JSON.stringify({ error: {
35
+ message: "Malformed JSON",
36
+ type: "invalid_request_error",
37
+ code: "invalid_json"
38
+ } }));
39
+ return;
40
+ }
41
+ const syntheticReq = {
42
+ model: speechReq.model ?? "tts-1",
43
+ messages: [{
44
+ role: "user",
45
+ content: speechReq.input
46
+ }],
47
+ _endpointType: "speech"
48
+ };
49
+ const testId = require_helpers.getTestId(req);
50
+ const fixture = require_router.matchFixture(fixtures, syntheticReq, journal.getFixtureMatchCountsForTest(testId), defaults.requestTransform);
51
+ if (fixture) journal.incrementFixtureMatchCount(fixture, fixtures, testId);
52
+ if (require_chaos.applyChaos(res, fixture, defaults.chaos, req.headers, journal, {
53
+ method,
54
+ path,
55
+ headers: require_helpers.flattenHeaders(req.headers),
56
+ body: syntheticReq
57
+ }, defaults.registry, defaults.logger)) return;
58
+ if (!fixture) {
59
+ if (defaults.record) {
60
+ if (await require_recorder.proxyAndRecord(req, res, syntheticReq, "openai", req.url ?? "/v1/audio/speech", fixtures, defaults, raw)) {
61
+ journal.add({
62
+ method,
63
+ path,
64
+ headers: require_helpers.flattenHeaders(req.headers),
65
+ body: syntheticReq,
66
+ response: {
67
+ status: res.statusCode ?? 200,
68
+ fixture: null
69
+ }
70
+ });
71
+ return;
72
+ }
73
+ }
74
+ const strictStatus = defaults.strict ? 503 : 404;
75
+ const strictMessage = defaults.strict ? "Strict mode: no fixture matched" : "No fixture matched";
76
+ journal.add({
77
+ method,
78
+ path,
79
+ headers: require_helpers.flattenHeaders(req.headers),
80
+ body: syntheticReq,
81
+ response: {
82
+ status: strictStatus,
83
+ fixture: null
84
+ }
85
+ });
86
+ require_sse_writer.writeErrorResponse(res, strictStatus, JSON.stringify({ error: {
87
+ message: strictMessage,
88
+ type: "invalid_request_error",
89
+ code: "no_fixture_match"
90
+ } }));
91
+ return;
92
+ }
93
+ const response = fixture.response;
94
+ if (require_helpers.isErrorResponse(response)) {
95
+ const status = response.status ?? 500;
96
+ journal.add({
97
+ method,
98
+ path,
99
+ headers: require_helpers.flattenHeaders(req.headers),
100
+ body: syntheticReq,
101
+ response: {
102
+ status,
103
+ fixture
104
+ }
105
+ });
106
+ require_sse_writer.writeErrorResponse(res, status, JSON.stringify(response));
107
+ return;
108
+ }
109
+ if (!require_helpers.isAudioResponse(response)) {
110
+ journal.add({
111
+ method,
112
+ path,
113
+ headers: require_helpers.flattenHeaders(req.headers),
114
+ body: syntheticReq,
115
+ response: {
116
+ status: 500,
117
+ fixture
118
+ }
119
+ });
120
+ require_sse_writer.writeErrorResponse(res, 500, JSON.stringify({ error: {
121
+ message: "Fixture response is not an audio type",
122
+ type: "server_error"
123
+ } }));
124
+ return;
125
+ }
126
+ journal.add({
127
+ method,
128
+ path,
129
+ headers: require_helpers.flattenHeaders(req.headers),
130
+ body: syntheticReq,
131
+ response: {
132
+ status: 200,
133
+ fixture
134
+ }
135
+ });
136
+ const contentType = FORMAT_TO_CONTENT_TYPE[response.format ?? "mp3"] ?? "audio/mpeg";
137
+ const audioBytes = Buffer.from(response.audio, "base64");
138
+ res.writeHead(200, { "Content-Type": contentType });
139
+ res.end(audioBytes);
140
+ }
141
+
142
+ //#endregion
143
+ exports.handleSpeech = handleSpeech;
144
+ //# sourceMappingURL=speech.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"speech.cjs","names":["flattenHeaders","getTestId","matchFixture","applyChaos","proxyAndRecord","isErrorResponse","isAudioResponse"],"sources":["../src/speech.ts"],"sourcesContent":["import type * as http from \"node:http\";\nimport type { ChatCompletionRequest, Fixture, HandlerDefaults } from \"./types.js\";\nimport { isAudioResponse, isErrorResponse, flattenHeaders, getTestId } 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\ninterface SpeechRequest {\n model?: string;\n input: string;\n voice?: string;\n response_format?: string;\n speed?: number;\n [key: string]: unknown;\n}\n\nconst FORMAT_TO_CONTENT_TYPE: Record<string, string> = {\n mp3: \"audio/mpeg\",\n opus: \"audio/opus\",\n aac: \"audio/aac\",\n flac: \"audio/flac\",\n wav: \"audio/wav\",\n pcm: \"audio/pcm\",\n};\n\nexport async function handleSpeech(\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 setCorsHeaders(res);\n const path = req.url ?? \"/v1/audio/speech\";\n const method = req.method ?? \"POST\";\n\n let speechReq: SpeechRequest;\n try {\n speechReq = JSON.parse(raw) as SpeechRequest;\n } catch {\n journal.add({\n method,\n path,\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: { message: \"Malformed JSON\", type: \"invalid_request_error\", code: \"invalid_json\" },\n }),\n );\n return;\n }\n\n const syntheticReq: ChatCompletionRequest = {\n model: speechReq.model ?? \"tts-1\",\n messages: [{ role: \"user\", content: speechReq.input }],\n _endpointType: \"speech\",\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 { method, path, headers: flattenHeaders(req.headers), body: syntheticReq },\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 syntheticReq,\n \"openai\",\n req.url ?? \"/v1/audio/speech\",\n fixtures,\n defaults,\n raw,\n );\n if (proxied) {\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: { status: res.statusCode ?? 200, fixture: null },\n });\n return;\n }\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 journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: { status: strictStatus, fixture: null },\n });\n writeErrorResponse(\n res,\n strictStatus,\n JSON.stringify({\n error: { message: strictMessage, type: \"invalid_request_error\", code: \"no_fixture_match\" },\n }),\n );\n return;\n }\n\n const response = fixture.response;\n\n if (isErrorResponse(response)) {\n const status = response.status ?? 500;\n journal.add({\n method,\n path,\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 if (!isAudioResponse(response)) {\n journal.add({\n method,\n path,\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: { message: \"Fixture response is not an audio type\", type: \"server_error\" },\n }),\n );\n return;\n }\n\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: { status: 200, fixture },\n });\n\n const format = response.format ?? \"mp3\";\n const contentType = FORMAT_TO_CONTENT_TYPE[format] ?? \"audio/mpeg\";\n const audioBytes = Buffer.from(response.audio, \"base64\");\n\n res.writeHead(200, { \"Content-Type\": contentType });\n res.end(audioBytes);\n}\n"],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ import { Journal } from "./journal.cjs";
2
+ import { Fixture, HandlerDefaults } from "./types.cjs";
3
+ import * as http from "node:http";
4
+
5
+ //#region src/speech.d.ts
6
+ declare function handleSpeech(req: http.IncomingMessage, res: http.ServerResponse, raw: string, fixtures: Fixture[], journal: Journal, defaults: HandlerDefaults, setCorsHeaders: (res: http.ServerResponse) => void): Promise<void>;
7
+ //# sourceMappingURL=speech.d.ts.map
8
+
9
+ //#endregion
10
+ export { handleSpeech };
11
+ //# sourceMappingURL=speech.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"speech.d.cts","names":[],"sources":["../src/speech.ts"],"sourcesContent":[],"mappings":";;;;;iBA2BsB,YAAA,MACf,IAAA,CAAK,sBACL,IAAA,CAAK,uCAEA,oBACD,mBACC,uCACY,IAAA,CAAK,0BAC1B"}
@@ -0,0 +1,11 @@
1
+ import { Journal } from "./journal.js";
2
+ import { Fixture, HandlerDefaults } from "./types.js";
3
+ import * as http from "node:http";
4
+
5
+ //#region src/speech.d.ts
6
+ declare function handleSpeech(req: http.IncomingMessage, res: http.ServerResponse, raw: string, fixtures: Fixture[], journal: Journal, defaults: HandlerDefaults, setCorsHeaders: (res: http.ServerResponse) => void): Promise<void>;
7
+ //# sourceMappingURL=speech.d.ts.map
8
+
9
+ //#endregion
10
+ export { handleSpeech };
11
+ //# sourceMappingURL=speech.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"speech.d.ts","names":[],"sources":["../src/speech.ts"],"sourcesContent":[],"mappings":";;;;;iBA2BsB,YAAA,MACf,IAAA,CAAK,sBACL,IAAA,CAAK,uCAEA,oBACD,mBACC,uCACY,IAAA,CAAK,0BAC1B"}
package/dist/speech.js ADDED
@@ -0,0 +1,144 @@
1
+ import { flattenHeaders, getTestId, isAudioResponse, isErrorResponse } from "./helpers.js";
2
+ import { matchFixture } from "./router.js";
3
+ import { writeErrorResponse } from "./sse-writer.js";
4
+ import { applyChaos } from "./chaos.js";
5
+ import { proxyAndRecord } from "./recorder.js";
6
+
7
+ //#region src/speech.ts
8
+ const FORMAT_TO_CONTENT_TYPE = {
9
+ mp3: "audio/mpeg",
10
+ opus: "audio/opus",
11
+ aac: "audio/aac",
12
+ flac: "audio/flac",
13
+ wav: "audio/wav",
14
+ pcm: "audio/pcm"
15
+ };
16
+ async function handleSpeech(req, res, raw, fixtures, journal, defaults, setCorsHeaders) {
17
+ setCorsHeaders(res);
18
+ const path = req.url ?? "/v1/audio/speech";
19
+ const method = req.method ?? "POST";
20
+ let speechReq;
21
+ try {
22
+ speechReq = JSON.parse(raw);
23
+ } catch {
24
+ journal.add({
25
+ method,
26
+ path,
27
+ headers: flattenHeaders(req.headers),
28
+ body: null,
29
+ response: {
30
+ status: 400,
31
+ fixture: null
32
+ }
33
+ });
34
+ writeErrorResponse(res, 400, JSON.stringify({ error: {
35
+ message: "Malformed JSON",
36
+ type: "invalid_request_error",
37
+ code: "invalid_json"
38
+ } }));
39
+ return;
40
+ }
41
+ const syntheticReq = {
42
+ model: speechReq.model ?? "tts-1",
43
+ messages: [{
44
+ role: "user",
45
+ content: speechReq.input
46
+ }],
47
+ _endpointType: "speech"
48
+ };
49
+ const testId = getTestId(req);
50
+ const fixture = matchFixture(fixtures, syntheticReq, journal.getFixtureMatchCountsForTest(testId), defaults.requestTransform);
51
+ if (fixture) journal.incrementFixtureMatchCount(fixture, fixtures, testId);
52
+ if (applyChaos(res, fixture, defaults.chaos, req.headers, journal, {
53
+ method,
54
+ path,
55
+ headers: flattenHeaders(req.headers),
56
+ body: syntheticReq
57
+ }, defaults.registry, defaults.logger)) return;
58
+ if (!fixture) {
59
+ if (defaults.record) {
60
+ if (await proxyAndRecord(req, res, syntheticReq, "openai", req.url ?? "/v1/audio/speech", fixtures, defaults, raw)) {
61
+ journal.add({
62
+ method,
63
+ path,
64
+ headers: flattenHeaders(req.headers),
65
+ body: syntheticReq,
66
+ response: {
67
+ status: res.statusCode ?? 200,
68
+ fixture: null
69
+ }
70
+ });
71
+ return;
72
+ }
73
+ }
74
+ const strictStatus = defaults.strict ? 503 : 404;
75
+ const strictMessage = defaults.strict ? "Strict mode: no fixture matched" : "No fixture matched";
76
+ journal.add({
77
+ method,
78
+ path,
79
+ headers: flattenHeaders(req.headers),
80
+ body: syntheticReq,
81
+ response: {
82
+ status: strictStatus,
83
+ fixture: null
84
+ }
85
+ });
86
+ writeErrorResponse(res, strictStatus, JSON.stringify({ error: {
87
+ message: strictMessage,
88
+ type: "invalid_request_error",
89
+ code: "no_fixture_match"
90
+ } }));
91
+ return;
92
+ }
93
+ const response = fixture.response;
94
+ if (isErrorResponse(response)) {
95
+ const status = response.status ?? 500;
96
+ journal.add({
97
+ method,
98
+ path,
99
+ headers: flattenHeaders(req.headers),
100
+ body: syntheticReq,
101
+ response: {
102
+ status,
103
+ fixture
104
+ }
105
+ });
106
+ writeErrorResponse(res, status, JSON.stringify(response));
107
+ return;
108
+ }
109
+ if (!isAudioResponse(response)) {
110
+ journal.add({
111
+ method,
112
+ path,
113
+ headers: flattenHeaders(req.headers),
114
+ body: syntheticReq,
115
+ response: {
116
+ status: 500,
117
+ fixture
118
+ }
119
+ });
120
+ writeErrorResponse(res, 500, JSON.stringify({ error: {
121
+ message: "Fixture response is not an audio type",
122
+ type: "server_error"
123
+ } }));
124
+ return;
125
+ }
126
+ journal.add({
127
+ method,
128
+ path,
129
+ headers: flattenHeaders(req.headers),
130
+ body: syntheticReq,
131
+ response: {
132
+ status: 200,
133
+ fixture
134
+ }
135
+ });
136
+ const contentType = FORMAT_TO_CONTENT_TYPE[response.format ?? "mp3"] ?? "audio/mpeg";
137
+ const audioBytes = Buffer.from(response.audio, "base64");
138
+ res.writeHead(200, { "Content-Type": contentType });
139
+ res.end(audioBytes);
140
+ }
141
+
142
+ //#endregion
143
+ export { handleSpeech };
144
+ //# sourceMappingURL=speech.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"speech.js","names":[],"sources":["../src/speech.ts"],"sourcesContent":["import type * as http from \"node:http\";\nimport type { ChatCompletionRequest, Fixture, HandlerDefaults } from \"./types.js\";\nimport { isAudioResponse, isErrorResponse, flattenHeaders, getTestId } 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\ninterface SpeechRequest {\n model?: string;\n input: string;\n voice?: string;\n response_format?: string;\n speed?: number;\n [key: string]: unknown;\n}\n\nconst FORMAT_TO_CONTENT_TYPE: Record<string, string> = {\n mp3: \"audio/mpeg\",\n opus: \"audio/opus\",\n aac: \"audio/aac\",\n flac: \"audio/flac\",\n wav: \"audio/wav\",\n pcm: \"audio/pcm\",\n};\n\nexport async function handleSpeech(\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 setCorsHeaders(res);\n const path = req.url ?? \"/v1/audio/speech\";\n const method = req.method ?? \"POST\";\n\n let speechReq: SpeechRequest;\n try {\n speechReq = JSON.parse(raw) as SpeechRequest;\n } catch {\n journal.add({\n method,\n path,\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: { message: \"Malformed JSON\", type: \"invalid_request_error\", code: \"invalid_json\" },\n }),\n );\n return;\n }\n\n const syntheticReq: ChatCompletionRequest = {\n model: speechReq.model ?? \"tts-1\",\n messages: [{ role: \"user\", content: speechReq.input }],\n _endpointType: \"speech\",\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 { method, path, headers: flattenHeaders(req.headers), body: syntheticReq },\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 syntheticReq,\n \"openai\",\n req.url ?? \"/v1/audio/speech\",\n fixtures,\n defaults,\n raw,\n );\n if (proxied) {\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: { status: res.statusCode ?? 200, fixture: null },\n });\n return;\n }\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 journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: { status: strictStatus, fixture: null },\n });\n writeErrorResponse(\n res,\n strictStatus,\n JSON.stringify({\n error: { message: strictMessage, type: \"invalid_request_error\", code: \"no_fixture_match\" },\n }),\n );\n return;\n }\n\n const response = fixture.response;\n\n if (isErrorResponse(response)) {\n const status = response.status ?? 500;\n journal.add({\n method,\n path,\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 if (!isAudioResponse(response)) {\n journal.add({\n method,\n path,\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: { message: \"Fixture response is not an audio type\", type: \"server_error\" },\n }),\n );\n return;\n }\n\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: { status: 200, fixture },\n });\n\n const format = response.format ?? \"mp3\";\n const contentType = FORMAT_TO_CONTENT_TYPE[format] ?? \"audio/mpeg\";\n const audioBytes = Buffer.from(response.audio, \"base64\");\n\n res.writeHead(200, { \"Content-Type\": contentType });\n res.end(audioBytes);\n}\n"],"mappings":";;;;;;;AAkBA,MAAM,yBAAiD;CACrD,KAAK;CACL,MAAM;CACN,KAAK;CACL,MAAM;CACN,KAAK;CACL,KAAK;CACN;AAED,eAAsB,aACpB,KACA,KACA,KACA,UACA,SACA,UACA,gBACe;AACf,gBAAe,IAAI;CACnB,MAAM,OAAO,IAAI,OAAO;CACxB,MAAM,SAAS,IAAI,UAAU;CAE7B,IAAI;AACJ,KAAI;AACF,cAAY,KAAK,MAAM,IAAI;SACrB;AACN,UAAQ,IAAI;GACV;GACA;GACA,SAAS,eAAe,IAAI,QAAQ;GACpC,MAAM;GACN,UAAU;IAAE,QAAQ;IAAK,SAAS;IAAM;GACzC,CAAC;AACF,qBACE,KACA,KACA,KAAK,UAAU,EACb,OAAO;GAAE,SAAS;GAAkB,MAAM;GAAyB,MAAM;GAAgB,EAC1F,CAAC,CACH;AACD;;CAGF,MAAM,eAAsC;EAC1C,OAAO,UAAU,SAAS;EAC1B,UAAU,CAAC;GAAE,MAAM;GAAQ,SAAS,UAAU;GAAO,CAAC;EACtD,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;EAAE;EAAQ;EAAM,SAAS,eAAe,IAAI,QAAQ;EAAE,MAAM;EAAc,EAC1E,SAAS,UACT,SAAS,OACV,CAED;AAEF,KAAI,CAAC,SAAS;AACZ,MAAI,SAAS,QAWX;OAVgB,MAAM,eACpB,KACA,KACA,cACA,UACA,IAAI,OAAO,oBACX,UACA,UACA,IACD,EACY;AACX,YAAQ,IAAI;KACV;KACA;KACA,SAAS,eAAe,IAAI,QAAQ;KACpC,MAAM;KACN,UAAU;MAAE,QAAQ,IAAI,cAAc;MAAK,SAAS;MAAM;KAC3D,CAAC;AACF;;;EAIJ,MAAM,eAAe,SAAS,SAAS,MAAM;EAC7C,MAAM,gBAAgB,SAAS,SAC3B,oCACA;AACJ,UAAQ,IAAI;GACV;GACA;GACA,SAAS,eAAe,IAAI,QAAQ;GACpC,MAAM;GACN,UAAU;IAAE,QAAQ;IAAc,SAAS;IAAM;GAClD,CAAC;AACF,qBACE,KACA,cACA,KAAK,UAAU,EACb,OAAO;GAAE,SAAS;GAAe,MAAM;GAAyB,MAAM;GAAoB,EAC3F,CAAC,CACH;AACD;;CAGF,MAAM,WAAW,QAAQ;AAEzB,KAAI,gBAAgB,SAAS,EAAE;EAC7B,MAAM,SAAS,SAAS,UAAU;AAClC,UAAQ,IAAI;GACV;GACA;GACA,SAAS,eAAe,IAAI,QAAQ;GACpC,MAAM;GACN,UAAU;IAAE;IAAQ;IAAS;GAC9B,CAAC;AACF,qBAAmB,KAAK,QAAQ,KAAK,UAAU,SAAS,CAAC;AACzD;;AAGF,KAAI,CAAC,gBAAgB,SAAS,EAAE;AAC9B,UAAQ,IAAI;GACV;GACA;GACA,SAAS,eAAe,IAAI,QAAQ;GACpC,MAAM;GACN,UAAU;IAAE,QAAQ;IAAK;IAAS;GACnC,CAAC;AACF,qBACE,KACA,KACA,KAAK,UAAU,EACb,OAAO;GAAE,SAAS;GAAyC,MAAM;GAAgB,EAClF,CAAC,CACH;AACD;;AAGF,SAAQ,IAAI;EACV;EACA;EACA,SAAS,eAAe,IAAI,QAAQ;EACpC,MAAM;EACN,UAAU;GAAE,QAAQ;GAAK;GAAS;EACnC,CAAC;CAGF,MAAM,cAAc,uBADL,SAAS,UAAU,UACoB;CACtD,MAAM,aAAa,OAAO,KAAK,SAAS,OAAO,SAAS;AAExD,KAAI,UAAU,KAAK,EAAE,gBAAgB,aAAa,CAAC;AACnD,KAAI,IAAI,WAAW"}
@@ -0,0 +1,134 @@
1
+ const require_helpers = require('./helpers.cjs');
2
+ const require_router = require('./router.cjs');
3
+ const require_sse_writer = require('./sse-writer.cjs');
4
+ const require_chaos = require('./chaos.cjs');
5
+ const require_recorder = require('./recorder.cjs');
6
+
7
+ //#region src/transcription.ts
8
+ /**
9
+ * Extract a named field value from a multipart/form-data body.
10
+ * Lightweight parser — scans for Content-Disposition headers
11
+ * to find simple string field values.
12
+ */
13
+ function extractFormField(raw, fieldName) {
14
+ const pattern = new RegExp(`Content-Disposition:\\s*form-data;[^\\r\\n]*name="${fieldName}"[^\\r\\n]*\\r\\n\\r\\n([^\\r\\n]*)`, "i");
15
+ return raw.match(pattern)?.[1];
16
+ }
17
+ async function handleTranscription(req, res, raw, fixtures, journal, defaults, setCorsHeaders) {
18
+ setCorsHeaders(res);
19
+ const path = req.url ?? "/v1/audio/transcriptions";
20
+ const method = req.method ?? "POST";
21
+ const model = extractFormField(raw, "model") ?? "whisper-1";
22
+ const responseFormat = extractFormField(raw, "response_format") ?? "json";
23
+ const syntheticReq = {
24
+ model,
25
+ messages: [],
26
+ _endpointType: "transcription"
27
+ };
28
+ const testId = require_helpers.getTestId(req);
29
+ const fixture = require_router.matchFixture(fixtures, syntheticReq, journal.getFixtureMatchCountsForTest(testId), defaults.requestTransform);
30
+ if (fixture) journal.incrementFixtureMatchCount(fixture, fixtures, testId);
31
+ if (require_chaos.applyChaos(res, fixture, defaults.chaos, req.headers, journal, {
32
+ method,
33
+ path,
34
+ headers: require_helpers.flattenHeaders(req.headers),
35
+ body: syntheticReq
36
+ }, defaults.registry, defaults.logger)) return;
37
+ if (!fixture) {
38
+ if (defaults.record) {
39
+ if (await require_recorder.proxyAndRecord(req, res, syntheticReq, "openai", req.url ?? "/v1/audio/transcriptions", fixtures, defaults, raw)) {
40
+ journal.add({
41
+ method,
42
+ path,
43
+ headers: require_helpers.flattenHeaders(req.headers),
44
+ body: syntheticReq,
45
+ response: {
46
+ status: res.statusCode ?? 200,
47
+ fixture: null
48
+ }
49
+ });
50
+ return;
51
+ }
52
+ }
53
+ const strictStatus = defaults.strict ? 503 : 404;
54
+ const strictMessage = defaults.strict ? "Strict mode: no fixture matched" : "No fixture matched";
55
+ journal.add({
56
+ method,
57
+ path,
58
+ headers: require_helpers.flattenHeaders(req.headers),
59
+ body: syntheticReq,
60
+ response: {
61
+ status: strictStatus,
62
+ fixture: null
63
+ }
64
+ });
65
+ require_sse_writer.writeErrorResponse(res, strictStatus, JSON.stringify({ error: {
66
+ message: strictMessage,
67
+ type: "invalid_request_error",
68
+ code: "no_fixture_match"
69
+ } }));
70
+ return;
71
+ }
72
+ const response = fixture.response;
73
+ if (require_helpers.isErrorResponse(response)) {
74
+ const status = response.status ?? 500;
75
+ journal.add({
76
+ method,
77
+ path,
78
+ headers: require_helpers.flattenHeaders(req.headers),
79
+ body: syntheticReq,
80
+ response: {
81
+ status,
82
+ fixture
83
+ }
84
+ });
85
+ require_sse_writer.writeErrorResponse(res, status, JSON.stringify(response));
86
+ return;
87
+ }
88
+ if (!require_helpers.isTranscriptionResponse(response)) {
89
+ journal.add({
90
+ method,
91
+ path,
92
+ headers: require_helpers.flattenHeaders(req.headers),
93
+ body: syntheticReq,
94
+ response: {
95
+ status: 500,
96
+ fixture
97
+ }
98
+ });
99
+ require_sse_writer.writeErrorResponse(res, 500, JSON.stringify({ error: {
100
+ message: "Fixture response is not a transcription type",
101
+ type: "server_error"
102
+ } }));
103
+ return;
104
+ }
105
+ journal.add({
106
+ method,
107
+ path,
108
+ headers: require_helpers.flattenHeaders(req.headers),
109
+ body: syntheticReq,
110
+ response: {
111
+ status: 200,
112
+ fixture
113
+ }
114
+ });
115
+ const t = response.transcription;
116
+ if (responseFormat === "verbose_json" || t.words != null || t.segments != null) {
117
+ res.writeHead(200, { "Content-Type": "application/json" });
118
+ res.end(JSON.stringify({
119
+ task: "transcribe",
120
+ language: t.language ?? "english",
121
+ duration: t.duration ?? 0,
122
+ text: t.text,
123
+ words: t.words ?? [],
124
+ segments: t.segments ?? []
125
+ }));
126
+ } else {
127
+ res.writeHead(200, { "Content-Type": "application/json" });
128
+ res.end(JSON.stringify({ text: t.text }));
129
+ }
130
+ }
131
+
132
+ //#endregion
133
+ exports.handleTranscription = handleTranscription;
134
+ //# sourceMappingURL=transcription.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transcription.cjs","names":["getTestId","matchFixture","applyChaos","flattenHeaders","proxyAndRecord","isErrorResponse","isTranscriptionResponse"],"sources":["../src/transcription.ts"],"sourcesContent":["import type * as http from \"node:http\";\nimport type { ChatCompletionRequest, Fixture, HandlerDefaults } from \"./types.js\";\nimport { isTranscriptionResponse, isErrorResponse, flattenHeaders, getTestId } 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/**\n * Extract a named field value from a multipart/form-data body.\n * Lightweight parser — scans for Content-Disposition headers\n * to find simple string field values.\n */\nfunction extractFormField(raw: string, fieldName: string): string | undefined {\n const pattern = new RegExp(\n `Content-Disposition:\\\\s*form-data;[^\\\\r\\\\n]*name=\"${fieldName}\"[^\\\\r\\\\n]*\\\\r\\\\n\\\\r\\\\n([^\\\\r\\\\n]*)`,\n \"i\",\n );\n const match = raw.match(pattern);\n return match?.[1];\n}\n\nexport async function handleTranscription(\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 setCorsHeaders(res);\n const path = req.url ?? \"/v1/audio/transcriptions\";\n const method = req.method ?? \"POST\";\n\n const model = extractFormField(raw, \"model\") ?? \"whisper-1\";\n const responseFormat = extractFormField(raw, \"response_format\") ?? \"json\";\n\n const syntheticReq: ChatCompletionRequest = {\n model,\n messages: [],\n _endpointType: \"transcription\",\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 { method, path, headers: flattenHeaders(req.headers), body: syntheticReq },\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 syntheticReq,\n \"openai\",\n req.url ?? \"/v1/audio/transcriptions\",\n fixtures,\n defaults,\n raw,\n );\n if (proxied) {\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: { status: res.statusCode ?? 200, fixture: null },\n });\n return;\n }\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 journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: { status: strictStatus, fixture: null },\n });\n writeErrorResponse(\n res,\n strictStatus,\n JSON.stringify({\n error: {\n message: strictMessage,\n type: \"invalid_request_error\",\n code: \"no_fixture_match\",\n },\n }),\n );\n return;\n }\n\n const response = fixture.response;\n\n if (isErrorResponse(response)) {\n const status = response.status ?? 500;\n journal.add({\n method,\n path,\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 if (!isTranscriptionResponse(response)) {\n journal.add({\n method,\n path,\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: \"Fixture response is not a transcription type\",\n type: \"server_error\",\n },\n }),\n );\n return;\n }\n\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: { status: 200, fixture },\n });\n\n const t = response.transcription;\n const useVerbose = responseFormat === \"verbose_json\" || t.words != null || t.segments != null;\n\n if (useVerbose) {\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(\n JSON.stringify({\n task: \"transcribe\",\n language: t.language ?? \"english\",\n duration: t.duration ?? 0,\n text: t.text,\n words: t.words ?? [],\n segments: t.segments ?? [],\n }),\n );\n } else {\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ text: t.text }));\n }\n}\n"],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ import { Journal } from "./journal.cjs";
2
+ import { Fixture, HandlerDefaults } from "./types.cjs";
3
+ import * as http from "node:http";
4
+
5
+ //#region src/transcription.d.ts
6
+ declare function handleTranscription(req: http.IncomingMessage, res: http.ServerResponse, raw: string, fixtures: Fixture[], journal: Journal, defaults: HandlerDefaults, setCorsHeaders: (res: http.ServerResponse) => void): Promise<void>;
7
+ //# sourceMappingURL=transcription.d.ts.map
8
+
9
+ //#endregion
10
+ export { handleTranscription };
11
+ //# sourceMappingURL=transcription.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transcription.d.cts","names":[],"sources":["../src/transcription.ts"],"sourcesContent":[],"mappings":";;;;;iBAuBsB,mBAAA,MACf,IAAA,CAAK,sBACL,IAAA,CAAK,uCAEA,oBACD,mBACC,uCACY,IAAA,CAAK,0BAC1B"}
@@ -0,0 +1,11 @@
1
+ import { Journal } from "./journal.js";
2
+ import { Fixture, HandlerDefaults } from "./types.js";
3
+ import * as http from "node:http";
4
+
5
+ //#region src/transcription.d.ts
6
+ declare function handleTranscription(req: http.IncomingMessage, res: http.ServerResponse, raw: string, fixtures: Fixture[], journal: Journal, defaults: HandlerDefaults, setCorsHeaders: (res: http.ServerResponse) => void): Promise<void>;
7
+ //# sourceMappingURL=transcription.d.ts.map
8
+
9
+ //#endregion
10
+ export { handleTranscription };
11
+ //# sourceMappingURL=transcription.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transcription.d.ts","names":[],"sources":["../src/transcription.ts"],"sourcesContent":[],"mappings":";;;;;iBAuBsB,mBAAA,MACf,IAAA,CAAK,sBACL,IAAA,CAAK,uCAEA,oBACD,mBACC,uCACY,IAAA,CAAK,0BAC1B"}
@@ -0,0 +1,134 @@
1
+ import { flattenHeaders, getTestId, isErrorResponse, isTranscriptionResponse } from "./helpers.js";
2
+ import { matchFixture } from "./router.js";
3
+ import { writeErrorResponse } from "./sse-writer.js";
4
+ import { applyChaos } from "./chaos.js";
5
+ import { proxyAndRecord } from "./recorder.js";
6
+
7
+ //#region src/transcription.ts
8
+ /**
9
+ * Extract a named field value from a multipart/form-data body.
10
+ * Lightweight parser — scans for Content-Disposition headers
11
+ * to find simple string field values.
12
+ */
13
+ function extractFormField(raw, fieldName) {
14
+ const pattern = new RegExp(`Content-Disposition:\\s*form-data;[^\\r\\n]*name="${fieldName}"[^\\r\\n]*\\r\\n\\r\\n([^\\r\\n]*)`, "i");
15
+ return raw.match(pattern)?.[1];
16
+ }
17
+ async function handleTranscription(req, res, raw, fixtures, journal, defaults, setCorsHeaders) {
18
+ setCorsHeaders(res);
19
+ const path = req.url ?? "/v1/audio/transcriptions";
20
+ const method = req.method ?? "POST";
21
+ const model = extractFormField(raw, "model") ?? "whisper-1";
22
+ const responseFormat = extractFormField(raw, "response_format") ?? "json";
23
+ const syntheticReq = {
24
+ model,
25
+ messages: [],
26
+ _endpointType: "transcription"
27
+ };
28
+ const testId = getTestId(req);
29
+ const fixture = matchFixture(fixtures, syntheticReq, journal.getFixtureMatchCountsForTest(testId), defaults.requestTransform);
30
+ if (fixture) journal.incrementFixtureMatchCount(fixture, fixtures, testId);
31
+ if (applyChaos(res, fixture, defaults.chaos, req.headers, journal, {
32
+ method,
33
+ path,
34
+ headers: flattenHeaders(req.headers),
35
+ body: syntheticReq
36
+ }, defaults.registry, defaults.logger)) return;
37
+ if (!fixture) {
38
+ if (defaults.record) {
39
+ if (await proxyAndRecord(req, res, syntheticReq, "openai", req.url ?? "/v1/audio/transcriptions", fixtures, defaults, raw)) {
40
+ journal.add({
41
+ method,
42
+ path,
43
+ headers: flattenHeaders(req.headers),
44
+ body: syntheticReq,
45
+ response: {
46
+ status: res.statusCode ?? 200,
47
+ fixture: null
48
+ }
49
+ });
50
+ return;
51
+ }
52
+ }
53
+ const strictStatus = defaults.strict ? 503 : 404;
54
+ const strictMessage = defaults.strict ? "Strict mode: no fixture matched" : "No fixture matched";
55
+ journal.add({
56
+ method,
57
+ path,
58
+ headers: flattenHeaders(req.headers),
59
+ body: syntheticReq,
60
+ response: {
61
+ status: strictStatus,
62
+ fixture: null
63
+ }
64
+ });
65
+ writeErrorResponse(res, strictStatus, JSON.stringify({ error: {
66
+ message: strictMessage,
67
+ type: "invalid_request_error",
68
+ code: "no_fixture_match"
69
+ } }));
70
+ return;
71
+ }
72
+ const response = fixture.response;
73
+ if (isErrorResponse(response)) {
74
+ const status = response.status ?? 500;
75
+ journal.add({
76
+ method,
77
+ path,
78
+ headers: flattenHeaders(req.headers),
79
+ body: syntheticReq,
80
+ response: {
81
+ status,
82
+ fixture
83
+ }
84
+ });
85
+ writeErrorResponse(res, status, JSON.stringify(response));
86
+ return;
87
+ }
88
+ if (!isTranscriptionResponse(response)) {
89
+ journal.add({
90
+ method,
91
+ path,
92
+ headers: flattenHeaders(req.headers),
93
+ body: syntheticReq,
94
+ response: {
95
+ status: 500,
96
+ fixture
97
+ }
98
+ });
99
+ writeErrorResponse(res, 500, JSON.stringify({ error: {
100
+ message: "Fixture response is not a transcription type",
101
+ type: "server_error"
102
+ } }));
103
+ return;
104
+ }
105
+ journal.add({
106
+ method,
107
+ path,
108
+ headers: flattenHeaders(req.headers),
109
+ body: syntheticReq,
110
+ response: {
111
+ status: 200,
112
+ fixture
113
+ }
114
+ });
115
+ const t = response.transcription;
116
+ if (responseFormat === "verbose_json" || t.words != null || t.segments != null) {
117
+ res.writeHead(200, { "Content-Type": "application/json" });
118
+ res.end(JSON.stringify({
119
+ task: "transcribe",
120
+ language: t.language ?? "english",
121
+ duration: t.duration ?? 0,
122
+ text: t.text,
123
+ words: t.words ?? [],
124
+ segments: t.segments ?? []
125
+ }));
126
+ } else {
127
+ res.writeHead(200, { "Content-Type": "application/json" });
128
+ res.end(JSON.stringify({ text: t.text }));
129
+ }
130
+ }
131
+
132
+ //#endregion
133
+ export { handleTranscription };
134
+ //# sourceMappingURL=transcription.js.map