@copilotkit/aimock 1.17.0 → 1.18.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.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/CHANGELOG.md +11 -0
- package/dist/agui-types.d.ts.map +1 -1
- package/dist/bedrock-converse.cjs +4 -4
- package/dist/bedrock-converse.cjs.map +1 -1
- package/dist/bedrock-converse.d.cts.map +1 -1
- package/dist/bedrock-converse.d.ts.map +1 -1
- package/dist/bedrock-converse.js +4 -4
- package/dist/bedrock-converse.js.map +1 -1
- package/dist/bedrock.cjs +4 -4
- package/dist/bedrock.cjs.map +1 -1
- package/dist/bedrock.d.cts.map +1 -1
- package/dist/bedrock.d.ts.map +1 -1
- package/dist/bedrock.js +4 -4
- package/dist/bedrock.js.map +1 -1
- package/dist/chaos.cjs +35 -9
- package/dist/chaos.cjs.map +1 -1
- package/dist/chaos.d.cts +17 -2
- package/dist/chaos.d.cts.map +1 -1
- package/dist/chaos.d.ts +17 -2
- package/dist/chaos.d.ts.map +1 -1
- package/dist/chaos.js +35 -10
- package/dist/chaos.js.map +1 -1
- package/dist/cohere.cjs +2 -2
- package/dist/cohere.cjs.map +1 -1
- package/dist/cohere.js +2 -2
- package/dist/cohere.js.map +1 -1
- package/dist/elevenlabs-audio.cjs +5 -2
- package/dist/elevenlabs-audio.cjs.map +1 -1
- package/dist/elevenlabs-audio.js +5 -2
- package/dist/elevenlabs-audio.js.map +1 -1
- package/dist/embeddings.cjs +2 -2
- package/dist/embeddings.cjs.map +1 -1
- package/dist/embeddings.js +2 -2
- package/dist/embeddings.js.map +1 -1
- package/dist/fal-audio.cjs +11 -4
- package/dist/fal-audio.cjs.map +1 -1
- package/dist/fal-audio.js +11 -5
- package/dist/fal-audio.js.map +1 -1
- package/dist/fal.cjs +424 -0
- package/dist/fal.cjs.map +1 -0
- package/dist/fal.d.cts +39 -0
- package/dist/fal.d.cts.map +1 -0
- package/dist/fal.d.ts +39 -0
- package/dist/fal.d.ts.map +1 -0
- package/dist/fal.js +420 -0
- package/dist/fal.js.map +1 -0
- package/dist/fixture-loader.cjs +2 -2
- package/dist/fixture-loader.cjs.map +1 -1
- package/dist/fixture-loader.d.cts.map +1 -1
- package/dist/fixture-loader.d.ts.map +1 -1
- package/dist/fixture-loader.js +3 -3
- package/dist/fixture-loader.js.map +1 -1
- package/dist/gemini-interactions.cjs +4 -2
- package/dist/gemini-interactions.cjs.map +1 -1
- package/dist/gemini-interactions.js +4 -2
- package/dist/gemini-interactions.js.map +1 -1
- package/dist/gemini.cjs +2 -2
- package/dist/gemini.cjs.map +1 -1
- package/dist/gemini.js +2 -2
- package/dist/gemini.js.map +1 -1
- package/dist/helpers.cjs +4 -0
- package/dist/helpers.cjs.map +1 -1
- package/dist/helpers.d.cts.map +1 -1
- package/dist/helpers.d.ts.map +1 -1
- package/dist/helpers.js +4 -1
- package/dist/helpers.js.map +1 -1
- package/dist/images.cjs +2 -2
- package/dist/images.cjs.map +1 -1
- package/dist/images.js +2 -2
- package/dist/images.js.map +1 -1
- package/dist/index.cjs +3 -0
- package/dist/index.d.cts +3 -2
- package/dist/index.d.ts +3 -2
- package/dist/index.js +2 -1
- package/dist/llmock.cjs +15 -0
- package/dist/llmock.cjs.map +1 -1
- package/dist/llmock.d.cts +2 -0
- package/dist/llmock.d.cts.map +1 -1
- package/dist/llmock.d.ts +2 -0
- package/dist/llmock.d.ts.map +1 -1
- package/dist/llmock.js +15 -0
- package/dist/llmock.js.map +1 -1
- package/dist/messages.cjs +2 -2
- package/dist/messages.cjs.map +1 -1
- package/dist/messages.js +2 -2
- package/dist/messages.js.map +1 -1
- package/dist/ollama.cjs +4 -4
- package/dist/ollama.cjs.map +1 -1
- package/dist/ollama.d.cts.map +1 -1
- package/dist/ollama.d.ts.map +1 -1
- package/dist/ollama.js +4 -4
- package/dist/ollama.js.map +1 -1
- package/dist/recorder.cjs +42 -17
- package/dist/recorder.cjs.map +1 -1
- package/dist/recorder.d.cts +50 -5
- package/dist/recorder.d.cts.map +1 -1
- package/dist/recorder.d.ts +50 -5
- package/dist/recorder.d.ts.map +1 -1
- package/dist/recorder.js +42 -17
- package/dist/recorder.js.map +1 -1
- package/dist/responses.cjs +2 -2
- package/dist/responses.cjs.map +1 -1
- package/dist/responses.js +2 -2
- package/dist/responses.js.map +1 -1
- package/dist/router.cjs +1 -1
- package/dist/router.cjs.map +1 -1
- package/dist/router.d.cts.map +1 -1
- package/dist/router.d.ts.map +1 -1
- package/dist/router.js +2 -2
- package/dist/router.js.map +1 -1
- package/dist/server.cjs +128 -52
- package/dist/server.cjs.map +1 -1
- package/dist/server.d.cts.map +1 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +129 -53
- package/dist/server.js.map +1 -1
- package/dist/speech.cjs +2 -2
- package/dist/speech.cjs.map +1 -1
- package/dist/speech.js +2 -2
- package/dist/speech.js.map +1 -1
- package/dist/transcription.cjs +2 -2
- package/dist/transcription.cjs.map +1 -1
- package/dist/transcription.js +2 -2
- package/dist/transcription.js.map +1 -1
- package/dist/types.d.cts +30 -6
- package/dist/types.d.cts.map +1 -1
- package/dist/types.d.ts +30 -6
- package/dist/types.d.ts.map +1 -1
- package/dist/vector-types.d.cts.map +1 -1
- package/dist/video.cjs +9 -3
- package/dist/video.cjs.map +1 -1
- package/dist/video.d.cts +1 -1
- package/dist/video.d.cts.map +1 -1
- package/dist/video.d.ts +1 -1
- package/dist/video.d.ts.map +1 -1
- package/dist/video.js +9 -3
- package/dist/video.js.map +1 -1
- package/package.json +1 -1
package/dist/chaos.cjs
CHANGED
|
@@ -60,11 +60,33 @@ function evaluateChaos(fixture, serverDefaults, rawHeaders, logger) {
|
|
|
60
60
|
/**
|
|
61
61
|
* Apply chaos to a request. Returns true if chaos was applied (caller should
|
|
62
62
|
* return early), false if the request should proceed normally.
|
|
63
|
+
*
|
|
64
|
+
* `source` is required so the invariant "this handler only applies chaos in
|
|
65
|
+
* the <X> phase" is enforced at the type level. A future handler that grows
|
|
66
|
+
* a proxy path MUST pass `"proxy"` explicitly; the default can't drift silently.
|
|
63
67
|
*/
|
|
64
|
-
function applyChaos(res, fixture, serverDefaults, rawHeaders, journal, context, registry, logger) {
|
|
68
|
+
function applyChaos(res, fixture, serverDefaults, rawHeaders, journal, context, source, registry, logger) {
|
|
65
69
|
const action = evaluateChaos(fixture, serverDefaults, rawHeaders, logger);
|
|
66
70
|
if (!action) return false;
|
|
67
|
-
|
|
71
|
+
applyChaosAction(action, res, fixture, journal, context, source, registry);
|
|
72
|
+
return true;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Apply a specific (already-rolled) chaos action. Exposed so callers that roll
|
|
76
|
+
* the dice themselves can dispatch without re-rolling — important when the
|
|
77
|
+
* caller wants to branch on the action before committing (e.g. pre-flight vs.
|
|
78
|
+
* post-response phases).
|
|
79
|
+
*
|
|
80
|
+
* `source` is required (not optional) so callers can't silently omit it on
|
|
81
|
+
* one branch and journal an ambiguous entry. Pass `"fixture"` when a fixture
|
|
82
|
+
* matched (or would have) and `"proxy"` when the request was headed for the
|
|
83
|
+
* proxy path.
|
|
84
|
+
*/
|
|
85
|
+
function applyChaosAction(action, res, fixture, journal, context, source, registry) {
|
|
86
|
+
if (registry) registry.incrementCounter("aimock_chaos_triggered_total", {
|
|
87
|
+
action,
|
|
88
|
+
source
|
|
89
|
+
});
|
|
68
90
|
switch (action) {
|
|
69
91
|
case "drop":
|
|
70
92
|
journal.add({
|
|
@@ -72,7 +94,8 @@ function applyChaos(res, fixture, serverDefaults, rawHeaders, journal, context,
|
|
|
72
94
|
response: {
|
|
73
95
|
status: 500,
|
|
74
96
|
fixture,
|
|
75
|
-
chaosAction: "drop"
|
|
97
|
+
chaosAction: "drop",
|
|
98
|
+
source
|
|
76
99
|
}
|
|
77
100
|
});
|
|
78
101
|
require_sse_writer.writeErrorResponse(res, 500, JSON.stringify({ error: {
|
|
@@ -80,35 +103,38 @@ function applyChaos(res, fixture, serverDefaults, rawHeaders, journal, context,
|
|
|
80
103
|
type: "server_error",
|
|
81
104
|
code: "chaos_drop"
|
|
82
105
|
} }));
|
|
83
|
-
return
|
|
106
|
+
return;
|
|
84
107
|
case "malformed":
|
|
85
108
|
journal.add({
|
|
86
109
|
...context,
|
|
87
110
|
response: {
|
|
88
111
|
status: 200,
|
|
89
112
|
fixture,
|
|
90
|
-
chaosAction: "malformed"
|
|
113
|
+
chaosAction: "malformed",
|
|
114
|
+
source
|
|
91
115
|
}
|
|
92
116
|
});
|
|
93
117
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
94
118
|
res.end("{malformed json: <<<chaos>>>");
|
|
95
|
-
return
|
|
119
|
+
return;
|
|
96
120
|
case "disconnect":
|
|
97
121
|
journal.add({
|
|
98
122
|
...context,
|
|
99
123
|
response: {
|
|
100
124
|
status: 0,
|
|
101
125
|
fixture,
|
|
102
|
-
chaosAction: "disconnect"
|
|
126
|
+
chaosAction: "disconnect",
|
|
127
|
+
source
|
|
103
128
|
}
|
|
104
129
|
});
|
|
105
130
|
res.destroy();
|
|
106
|
-
return
|
|
107
|
-
default: return
|
|
131
|
+
return;
|
|
132
|
+
default: return;
|
|
108
133
|
}
|
|
109
134
|
}
|
|
110
135
|
|
|
111
136
|
//#endregion
|
|
112
137
|
exports.applyChaos = applyChaos;
|
|
138
|
+
exports.applyChaosAction = applyChaosAction;
|
|
113
139
|
exports.evaluateChaos = evaluateChaos;
|
|
114
140
|
//# sourceMappingURL=chaos.cjs.map
|
package/dist/chaos.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"chaos.cjs","names":[],"sources":["../src/chaos.ts"],"sourcesContent":["/**\n * Chaos testing support for LLMock.\n *\n * Provides probabilistic failure injection — requests can be dropped (500),\n * returned with malformed JSON, or have the connection forcibly disconnected.\n *\n * Precedence: per-request headers > fixture-level config > server-level defaults.\n */\n\nimport type * as http from \"node:http\";\nimport type { ChaosAction, ChaosConfig, ChatCompletionRequest, Fixture } from \"./types.js\";\nimport { writeErrorResponse } from \"./sse-writer.js\";\nimport type { Journal } from \"./journal.js\";\nimport type { Logger } from \"./logger.js\";\nimport type { MetricsRegistry } from \"./metrics.js\";\n\n/**\n * Resolve chaos config from headers, fixture, and server defaults.\n * Header values override fixture values, which override server defaults.\n */\nfunction resolveChaosConfig(\n fixture: Fixture | null,\n serverDefaults?: ChaosConfig,\n rawHeaders?: http.IncomingHttpHeaders,\n logger?: Logger,\n): ChaosConfig {\n const base: ChaosConfig = { ...serverDefaults };\n\n // Fixture-level overrides server defaults\n if (fixture?.chaos) {\n if (fixture.chaos.dropRate !== undefined) base.dropRate = fixture.chaos.dropRate;\n if (fixture.chaos.malformedRate !== undefined) base.malformedRate = fixture.chaos.malformedRate;\n if (fixture.chaos.disconnectRate !== undefined)\n base.disconnectRate = fixture.chaos.disconnectRate;\n }\n\n // Header overrides everything\n if (rawHeaders) {\n const dropHeader = rawHeaders[\"x-aimock-chaos-drop\"];\n const malformedHeader = rawHeaders[\"x-aimock-chaos-malformed\"];\n const disconnectHeader = rawHeaders[\"x-aimock-chaos-disconnect\"];\n\n if (typeof dropHeader === \"string\") {\n const val = parseFloat(dropHeader);\n if (isNaN(val)) {\n logger?.warn(`[chaos] x-aimock-chaos-drop: invalid value \"${dropHeader}\", ignoring`);\n } else {\n if (val < 0 || val > 1) {\n logger?.warn(`[chaos] x-aimock-chaos-drop: value ${val} out of range [0,1], clamping`);\n }\n base.dropRate = Math.min(1, Math.max(0, val));\n }\n }\n if (typeof malformedHeader === \"string\") {\n const val = parseFloat(malformedHeader);\n if (isNaN(val)) {\n logger?.warn(\n `[chaos] x-aimock-chaos-malformed: invalid value \"${malformedHeader}\", ignoring`,\n );\n } else {\n if (val < 0 || val > 1) {\n logger?.warn(\n `[chaos] x-aimock-chaos-malformed: value ${val} out of range [0,1], clamping`,\n );\n }\n base.malformedRate = Math.min(1, Math.max(0, val));\n }\n }\n if (typeof disconnectHeader === \"string\") {\n const val = parseFloat(disconnectHeader);\n if (isNaN(val)) {\n logger?.warn(\n `[chaos] x-aimock-chaos-disconnect: invalid value \"${disconnectHeader}\", ignoring`,\n );\n } else {\n if (val < 0 || val > 1) {\n logger?.warn(\n `[chaos] x-aimock-chaos-disconnect: value ${val} out of range [0,1], clamping`,\n );\n }\n base.disconnectRate = Math.min(1, Math.max(0, val));\n }\n }\n }\n\n // Clamp all resolved rates to [0, 1] regardless of source.\n // Header values are already clamped above; this covers fixture-level and server defaults.\n if (base.dropRate !== undefined) base.dropRate = Math.min(1, Math.max(0, base.dropRate));\n if (base.malformedRate !== undefined)\n base.malformedRate = Math.min(1, Math.max(0, base.malformedRate));\n if (base.disconnectRate !== undefined)\n base.disconnectRate = Math.min(1, Math.max(0, base.disconnectRate));\n\n return base;\n}\n\n/**\n * Evaluate chaos config and return the triggered action, or null if none.\n * Checks in order: drop, malformed, disconnect — first hit wins.\n */\nexport function evaluateChaos(\n fixture: Fixture | null,\n serverDefaults?: ChaosConfig,\n rawHeaders?: http.IncomingHttpHeaders,\n logger?: Logger,\n): ChaosAction | null {\n const config = resolveChaosConfig(fixture, serverDefaults, rawHeaders, logger);\n\n if (config.dropRate !== undefined && config.dropRate > 0 && Math.random() < config.dropRate) {\n return \"drop\";\n }\n if (\n config.malformedRate !== undefined &&\n config.malformedRate > 0 &&\n Math.random() < config.malformedRate\n ) {\n return \"malformed\";\n }\n if (\n config.disconnectRate !== undefined &&\n config.disconnectRate > 0 &&\n Math.random() < config.disconnectRate\n ) {\n return \"disconnect\";\n }\n\n return null;\n}\n\ninterface ChaosJournalContext {\n method: string;\n path: string;\n headers: Record<string, string>;\n body: ChatCompletionRequest;\n}\n\n/**\n * Apply chaos to a request. Returns true if chaos was applied (caller should\n * return early), false if the request should proceed normally.\n */\nexport function applyChaos(\n res: http.ServerResponse,\n fixture: Fixture | null,\n serverDefaults: ChaosConfig | undefined,\n rawHeaders: http.IncomingHttpHeaders,\n journal: Journal,\n context: ChaosJournalContext,\n registry?: MetricsRegistry,\n logger?: Logger,\n): boolean {\n const action = evaluateChaos(fixture, serverDefaults, rawHeaders, logger);\n if (!action) return false;\n\n if (registry) {\n registry.incrementCounter(\"aimock_chaos_triggered_total\", { action });\n }\n\n switch (action) {\n case \"drop\": {\n journal.add({\n ...context,\n response: { status: 500, fixture, chaosAction: \"drop\" },\n });\n writeErrorResponse(\n res,\n 500,\n JSON.stringify({\n error: {\n message: \"Chaos: request dropped\",\n type: \"server_error\",\n code: \"chaos_drop\",\n },\n }),\n );\n return true;\n }\n case \"malformed\": {\n journal.add({\n ...context,\n response: { status: 200, fixture, chaosAction: \"malformed\" },\n });\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(\"{malformed json: <<<chaos>>>\");\n return true;\n }\n case \"disconnect\": {\n journal.add({\n ...context,\n response: { status: 0, fixture, chaosAction: \"disconnect\" },\n });\n res.destroy();\n return true;\n }\n default: {\n const _exhaustive: never = action;\n void _exhaustive;\n return false;\n }\n }\n}\n"],"mappings":";;;;;;;AAoBA,SAAS,mBACP,SACA,gBACA,YACA,QACa;CACb,MAAM,OAAoB,EAAE,GAAG,gBAAgB;AAG/C,KAAI,SAAS,OAAO;AAClB,MAAI,QAAQ,MAAM,aAAa,OAAW,MAAK,WAAW,QAAQ,MAAM;AACxE,MAAI,QAAQ,MAAM,kBAAkB,OAAW,MAAK,gBAAgB,QAAQ,MAAM;AAClF,MAAI,QAAQ,MAAM,mBAAmB,OACnC,MAAK,iBAAiB,QAAQ,MAAM;;AAIxC,KAAI,YAAY;EACd,MAAM,aAAa,WAAW;EAC9B,MAAM,kBAAkB,WAAW;EACnC,MAAM,mBAAmB,WAAW;AAEpC,MAAI,OAAO,eAAe,UAAU;GAClC,MAAM,MAAM,WAAW,WAAW;AAClC,OAAI,MAAM,IAAI,CACZ,SAAQ,KAAK,+CAA+C,WAAW,aAAa;QAC/E;AACL,QAAI,MAAM,KAAK,MAAM,EACnB,SAAQ,KAAK,sCAAsC,IAAI,+BAA+B;AAExF,SAAK,WAAW,KAAK,IAAI,GAAG,KAAK,IAAI,GAAG,IAAI,CAAC;;;AAGjD,MAAI,OAAO,oBAAoB,UAAU;GACvC,MAAM,MAAM,WAAW,gBAAgB;AACvC,OAAI,MAAM,IAAI,CACZ,SAAQ,KACN,oDAAoD,gBAAgB,aACrE;QACI;AACL,QAAI,MAAM,KAAK,MAAM,EACnB,SAAQ,KACN,2CAA2C,IAAI,+BAChD;AAEH,SAAK,gBAAgB,KAAK,IAAI,GAAG,KAAK,IAAI,GAAG,IAAI,CAAC;;;AAGtD,MAAI,OAAO,qBAAqB,UAAU;GACxC,MAAM,MAAM,WAAW,iBAAiB;AACxC,OAAI,MAAM,IAAI,CACZ,SAAQ,KACN,qDAAqD,iBAAiB,aACvE;QACI;AACL,QAAI,MAAM,KAAK,MAAM,EACnB,SAAQ,KACN,4CAA4C,IAAI,+BACjD;AAEH,SAAK,iBAAiB,KAAK,IAAI,GAAG,KAAK,IAAI,GAAG,IAAI,CAAC;;;;AAOzD,KAAI,KAAK,aAAa,OAAW,MAAK,WAAW,KAAK,IAAI,GAAG,KAAK,IAAI,GAAG,KAAK,SAAS,CAAC;AACxF,KAAI,KAAK,kBAAkB,OACzB,MAAK,gBAAgB,KAAK,IAAI,GAAG,KAAK,IAAI,GAAG,KAAK,cAAc,CAAC;AACnE,KAAI,KAAK,mBAAmB,OAC1B,MAAK,iBAAiB,KAAK,IAAI,GAAG,KAAK,IAAI,GAAG,KAAK,eAAe,CAAC;AAErE,QAAO;;;;;;AAOT,SAAgB,cACd,SACA,gBACA,YACA,QACoB;CACpB,MAAM,SAAS,mBAAmB,SAAS,gBAAgB,YAAY,OAAO;AAE9E,KAAI,OAAO,aAAa,UAAa,OAAO,WAAW,KAAK,KAAK,QAAQ,GAAG,OAAO,SACjF,QAAO;AAET,KACE,OAAO,kBAAkB,UACzB,OAAO,gBAAgB,KACvB,KAAK,QAAQ,GAAG,OAAO,cAEvB,QAAO;AAET,KACE,OAAO,mBAAmB,UAC1B,OAAO,iBAAiB,KACxB,KAAK,QAAQ,GAAG,OAAO,eAEvB,QAAO;AAGT,QAAO;;;;;;AAcT,SAAgB,WACd,KACA,SACA,gBACA,YACA,SACA,SACA,UACA,QACS;CACT,MAAM,SAAS,cAAc,SAAS,gBAAgB,YAAY,OAAO;AACzE,KAAI,CAAC,OAAQ,QAAO;AAEpB,KAAI,SACF,UAAS,iBAAiB,gCAAgC,EAAE,QAAQ,CAAC;AAGvE,SAAQ,QAAR;EACE,KAAK;AACH,WAAQ,IAAI;IACV,GAAG;IACH,UAAU;KAAE,QAAQ;KAAK;KAAS,aAAa;KAAQ;IACxD,CAAC;AACF,yCACE,KACA,KACA,KAAK,UAAU,EACb,OAAO;IACL,SAAS;IACT,MAAM;IACN,MAAM;IACP,EACF,CAAC,CACH;AACD,UAAO;EAET,KAAK;AACH,WAAQ,IAAI;IACV,GAAG;IACH,UAAU;KAAE,QAAQ;KAAK;KAAS,aAAa;KAAa;IAC7D,CAAC;AACF,OAAI,UAAU,KAAK,EAAE,gBAAgB,oBAAoB,CAAC;AAC1D,OAAI,IAAI,+BAA+B;AACvC,UAAO;EAET,KAAK;AACH,WAAQ,IAAI;IACV,GAAG;IACH,UAAU;KAAE,QAAQ;KAAG;KAAS,aAAa;KAAc;IAC5D,CAAC;AACF,OAAI,SAAS;AACb,UAAO;EAET,QAGE,QAAO"}
|
|
1
|
+
{"version":3,"file":"chaos.cjs","names":[],"sources":["../src/chaos.ts"],"sourcesContent":["/**\n * Chaos testing support for LLMock.\n *\n * Provides probabilistic failure injection — requests can be dropped (500),\n * returned with malformed JSON, or have the connection forcibly disconnected.\n *\n * Precedence: per-request headers > fixture-level config > server-level defaults.\n */\n\nimport type * as http from \"node:http\";\nimport type { ChaosAction, ChaosConfig, ChatCompletionRequest, Fixture } from \"./types.js\";\nimport { writeErrorResponse } from \"./sse-writer.js\";\nimport type { Journal } from \"./journal.js\";\nimport type { Logger } from \"./logger.js\";\nimport type { MetricsRegistry } from \"./metrics.js\";\n\n/**\n * Resolve chaos config from headers, fixture, and server defaults.\n * Header values override fixture values, which override server defaults.\n */\nfunction resolveChaosConfig(\n fixture: Fixture | null,\n serverDefaults?: ChaosConfig,\n rawHeaders?: http.IncomingHttpHeaders,\n logger?: Logger,\n): ChaosConfig {\n const base: ChaosConfig = { ...serverDefaults };\n\n // Fixture-level overrides server defaults\n if (fixture?.chaos) {\n if (fixture.chaos.dropRate !== undefined) base.dropRate = fixture.chaos.dropRate;\n if (fixture.chaos.malformedRate !== undefined) base.malformedRate = fixture.chaos.malformedRate;\n if (fixture.chaos.disconnectRate !== undefined)\n base.disconnectRate = fixture.chaos.disconnectRate;\n }\n\n // Header overrides everything\n if (rawHeaders) {\n const dropHeader = rawHeaders[\"x-aimock-chaos-drop\"];\n const malformedHeader = rawHeaders[\"x-aimock-chaos-malformed\"];\n const disconnectHeader = rawHeaders[\"x-aimock-chaos-disconnect\"];\n\n if (typeof dropHeader === \"string\") {\n const val = parseFloat(dropHeader);\n if (isNaN(val)) {\n logger?.warn(`[chaos] x-aimock-chaos-drop: invalid value \"${dropHeader}\", ignoring`);\n } else {\n if (val < 0 || val > 1) {\n logger?.warn(`[chaos] x-aimock-chaos-drop: value ${val} out of range [0,1], clamping`);\n }\n base.dropRate = Math.min(1, Math.max(0, val));\n }\n }\n if (typeof malformedHeader === \"string\") {\n const val = parseFloat(malformedHeader);\n if (isNaN(val)) {\n logger?.warn(\n `[chaos] x-aimock-chaos-malformed: invalid value \"${malformedHeader}\", ignoring`,\n );\n } else {\n if (val < 0 || val > 1) {\n logger?.warn(\n `[chaos] x-aimock-chaos-malformed: value ${val} out of range [0,1], clamping`,\n );\n }\n base.malformedRate = Math.min(1, Math.max(0, val));\n }\n }\n if (typeof disconnectHeader === \"string\") {\n const val = parseFloat(disconnectHeader);\n if (isNaN(val)) {\n logger?.warn(\n `[chaos] x-aimock-chaos-disconnect: invalid value \"${disconnectHeader}\", ignoring`,\n );\n } else {\n if (val < 0 || val > 1) {\n logger?.warn(\n `[chaos] x-aimock-chaos-disconnect: value ${val} out of range [0,1], clamping`,\n );\n }\n base.disconnectRate = Math.min(1, Math.max(0, val));\n }\n }\n }\n\n // Clamp all resolved rates to [0, 1] regardless of source.\n // Header values are already clamped above; this covers fixture-level and server defaults.\n if (base.dropRate !== undefined) base.dropRate = Math.min(1, Math.max(0, base.dropRate));\n if (base.malformedRate !== undefined)\n base.malformedRate = Math.min(1, Math.max(0, base.malformedRate));\n if (base.disconnectRate !== undefined)\n base.disconnectRate = Math.min(1, Math.max(0, base.disconnectRate));\n\n return base;\n}\n\n/**\n * Evaluate chaos config and return the triggered action, or null if none.\n * Checks in order: drop, malformed, disconnect — first hit wins.\n */\nexport function evaluateChaos(\n fixture: Fixture | null,\n serverDefaults?: ChaosConfig,\n rawHeaders?: http.IncomingHttpHeaders,\n logger?: Logger,\n): ChaosAction | null {\n const config = resolveChaosConfig(fixture, serverDefaults, rawHeaders, logger);\n\n if (config.dropRate !== undefined && config.dropRate > 0 && Math.random() < config.dropRate) {\n return \"drop\";\n }\n if (\n config.malformedRate !== undefined &&\n config.malformedRate > 0 &&\n Math.random() < config.malformedRate\n ) {\n return \"malformed\";\n }\n if (\n config.disconnectRate !== undefined &&\n config.disconnectRate > 0 &&\n Math.random() < config.disconnectRate\n ) {\n return \"disconnect\";\n }\n\n return null;\n}\n\ninterface ChaosJournalContext {\n method: string;\n path: string;\n headers: Record<string, string>;\n body: ChatCompletionRequest | null;\n}\n\n/**\n * Apply chaos to a request. Returns true if chaos was applied (caller should\n * return early), false if the request should proceed normally.\n *\n * `source` is required so the invariant \"this handler only applies chaos in\n * the <X> phase\" is enforced at the type level. A future handler that grows\n * a proxy path MUST pass `\"proxy\"` explicitly; the default can't drift silently.\n */\nexport function applyChaos(\n res: http.ServerResponse,\n fixture: Fixture | null,\n serverDefaults: ChaosConfig | undefined,\n rawHeaders: http.IncomingHttpHeaders,\n journal: Journal,\n context: ChaosJournalContext,\n source: \"fixture\" | \"proxy\" | \"internal\",\n registry?: MetricsRegistry,\n logger?: Logger,\n): boolean {\n const action = evaluateChaos(fixture, serverDefaults, rawHeaders, logger);\n if (!action) return false;\n applyChaosAction(action, res, fixture, journal, context, source, registry);\n return true;\n}\n\n/**\n * Apply a specific (already-rolled) chaos action. Exposed so callers that roll\n * the dice themselves can dispatch without re-rolling — important when the\n * caller wants to branch on the action before committing (e.g. pre-flight vs.\n * post-response phases).\n *\n * `source` is required (not optional) so callers can't silently omit it on\n * one branch and journal an ambiguous entry. Pass `\"fixture\"` when a fixture\n * matched (or would have) and `\"proxy\"` when the request was headed for the\n * proxy path.\n */\nexport function applyChaosAction(\n action: ChaosAction,\n res: http.ServerResponse,\n fixture: Fixture | null,\n journal: Journal,\n context: ChaosJournalContext,\n source: \"fixture\" | \"proxy\" | \"internal\",\n registry?: MetricsRegistry,\n): void {\n if (registry) {\n registry.incrementCounter(\"aimock_chaos_triggered_total\", { action, source });\n }\n\n switch (action) {\n case \"drop\": {\n journal.add({\n ...context,\n response: { status: 500, fixture, chaosAction: \"drop\", source },\n });\n writeErrorResponse(\n res,\n 500,\n JSON.stringify({\n error: {\n message: \"Chaos: request dropped\",\n type: \"server_error\",\n code: \"chaos_drop\",\n },\n }),\n );\n return;\n }\n case \"malformed\": {\n journal.add({\n ...context,\n response: { status: 200, fixture, chaosAction: \"malformed\", source },\n });\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(\"{malformed json: <<<chaos>>>\");\n return;\n }\n case \"disconnect\": {\n journal.add({\n ...context,\n response: { status: 0, fixture, chaosAction: \"disconnect\", source },\n });\n res.destroy();\n return;\n }\n default: {\n const _exhaustive: never = action;\n void _exhaustive;\n return;\n }\n }\n}\n"],"mappings":";;;;;;;AAoBA,SAAS,mBACP,SACA,gBACA,YACA,QACa;CACb,MAAM,OAAoB,EAAE,GAAG,gBAAgB;AAG/C,KAAI,SAAS,OAAO;AAClB,MAAI,QAAQ,MAAM,aAAa,OAAW,MAAK,WAAW,QAAQ,MAAM;AACxE,MAAI,QAAQ,MAAM,kBAAkB,OAAW,MAAK,gBAAgB,QAAQ,MAAM;AAClF,MAAI,QAAQ,MAAM,mBAAmB,OACnC,MAAK,iBAAiB,QAAQ,MAAM;;AAIxC,KAAI,YAAY;EACd,MAAM,aAAa,WAAW;EAC9B,MAAM,kBAAkB,WAAW;EACnC,MAAM,mBAAmB,WAAW;AAEpC,MAAI,OAAO,eAAe,UAAU;GAClC,MAAM,MAAM,WAAW,WAAW;AAClC,OAAI,MAAM,IAAI,CACZ,SAAQ,KAAK,+CAA+C,WAAW,aAAa;QAC/E;AACL,QAAI,MAAM,KAAK,MAAM,EACnB,SAAQ,KAAK,sCAAsC,IAAI,+BAA+B;AAExF,SAAK,WAAW,KAAK,IAAI,GAAG,KAAK,IAAI,GAAG,IAAI,CAAC;;;AAGjD,MAAI,OAAO,oBAAoB,UAAU;GACvC,MAAM,MAAM,WAAW,gBAAgB;AACvC,OAAI,MAAM,IAAI,CACZ,SAAQ,KACN,oDAAoD,gBAAgB,aACrE;QACI;AACL,QAAI,MAAM,KAAK,MAAM,EACnB,SAAQ,KACN,2CAA2C,IAAI,+BAChD;AAEH,SAAK,gBAAgB,KAAK,IAAI,GAAG,KAAK,IAAI,GAAG,IAAI,CAAC;;;AAGtD,MAAI,OAAO,qBAAqB,UAAU;GACxC,MAAM,MAAM,WAAW,iBAAiB;AACxC,OAAI,MAAM,IAAI,CACZ,SAAQ,KACN,qDAAqD,iBAAiB,aACvE;QACI;AACL,QAAI,MAAM,KAAK,MAAM,EACnB,SAAQ,KACN,4CAA4C,IAAI,+BACjD;AAEH,SAAK,iBAAiB,KAAK,IAAI,GAAG,KAAK,IAAI,GAAG,IAAI,CAAC;;;;AAOzD,KAAI,KAAK,aAAa,OAAW,MAAK,WAAW,KAAK,IAAI,GAAG,KAAK,IAAI,GAAG,KAAK,SAAS,CAAC;AACxF,KAAI,KAAK,kBAAkB,OACzB,MAAK,gBAAgB,KAAK,IAAI,GAAG,KAAK,IAAI,GAAG,KAAK,cAAc,CAAC;AACnE,KAAI,KAAK,mBAAmB,OAC1B,MAAK,iBAAiB,KAAK,IAAI,GAAG,KAAK,IAAI,GAAG,KAAK,eAAe,CAAC;AAErE,QAAO;;;;;;AAOT,SAAgB,cACd,SACA,gBACA,YACA,QACoB;CACpB,MAAM,SAAS,mBAAmB,SAAS,gBAAgB,YAAY,OAAO;AAE9E,KAAI,OAAO,aAAa,UAAa,OAAO,WAAW,KAAK,KAAK,QAAQ,GAAG,OAAO,SACjF,QAAO;AAET,KACE,OAAO,kBAAkB,UACzB,OAAO,gBAAgB,KACvB,KAAK,QAAQ,GAAG,OAAO,cAEvB,QAAO;AAET,KACE,OAAO,mBAAmB,UAC1B,OAAO,iBAAiB,KACxB,KAAK,QAAQ,GAAG,OAAO,eAEvB,QAAO;AAGT,QAAO;;;;;;;;;;AAkBT,SAAgB,WACd,KACA,SACA,gBACA,YACA,SACA,SACA,QACA,UACA,QACS;CACT,MAAM,SAAS,cAAc,SAAS,gBAAgB,YAAY,OAAO;AACzE,KAAI,CAAC,OAAQ,QAAO;AACpB,kBAAiB,QAAQ,KAAK,SAAS,SAAS,SAAS,QAAQ,SAAS;AAC1E,QAAO;;;;;;;;;;;;;AAcT,SAAgB,iBACd,QACA,KACA,SACA,SACA,SACA,QACA,UACM;AACN,KAAI,SACF,UAAS,iBAAiB,gCAAgC;EAAE;EAAQ;EAAQ,CAAC;AAG/E,SAAQ,QAAR;EACE,KAAK;AACH,WAAQ,IAAI;IACV,GAAG;IACH,UAAU;KAAE,QAAQ;KAAK;KAAS,aAAa;KAAQ;KAAQ;IAChE,CAAC;AACF,yCACE,KACA,KACA,KAAK,UAAU,EACb,OAAO;IACL,SAAS;IACT,MAAM;IACN,MAAM;IACP,EACF,CAAC,CACH;AACD;EAEF,KAAK;AACH,WAAQ,IAAI;IACV,GAAG;IACH,UAAU;KAAE,QAAQ;KAAK;KAAS,aAAa;KAAa;KAAQ;IACrE,CAAC;AACF,OAAI,UAAU,KAAK,EAAE,gBAAgB,oBAAoB,CAAC;AAC1D,OAAI,IAAI,+BAA+B;AACvC;EAEF,KAAK;AACH,WAAQ,IAAI;IACV,GAAG;IACH,UAAU;KAAE,QAAQ;KAAG;KAAS,aAAa;KAAc;KAAQ;IACpE,CAAC;AACF,OAAI,SAAS;AACb;EAEF,QAGE"}
|
package/dist/chaos.d.cts
CHANGED
|
@@ -15,13 +15,28 @@ interface ChaosJournalContext {
|
|
|
15
15
|
method: string;
|
|
16
16
|
path: string;
|
|
17
17
|
headers: Record<string, string>;
|
|
18
|
-
body: ChatCompletionRequest;
|
|
18
|
+
body: ChatCompletionRequest | null;
|
|
19
19
|
}
|
|
20
20
|
/**
|
|
21
21
|
* Apply chaos to a request. Returns true if chaos was applied (caller should
|
|
22
22
|
* return early), false if the request should proceed normally.
|
|
23
|
+
*
|
|
24
|
+
* `source` is required so the invariant "this handler only applies chaos in
|
|
25
|
+
* the <X> phase" is enforced at the type level. A future handler that grows
|
|
26
|
+
* a proxy path MUST pass `"proxy"` explicitly; the default can't drift silently.
|
|
27
|
+
*/
|
|
28
|
+
declare function applyChaos(res: http$1.ServerResponse, fixture: Fixture | null, serverDefaults: ChaosConfig | undefined, rawHeaders: http$1.IncomingHttpHeaders, journal: Journal, context: ChaosJournalContext, source: "fixture" | "proxy" | "internal", registry?: MetricsRegistry, logger?: Logger): boolean;
|
|
29
|
+
/**
|
|
30
|
+
* Apply a specific (already-rolled) chaos action. Exposed so callers that roll
|
|
31
|
+
* the dice themselves can dispatch without re-rolling — important when the
|
|
32
|
+
* caller wants to branch on the action before committing (e.g. pre-flight vs.
|
|
33
|
+
* post-response phases).
|
|
34
|
+
*
|
|
35
|
+
* `source` is required (not optional) so callers can't silently omit it on
|
|
36
|
+
* one branch and journal an ambiguous entry. Pass `"fixture"` when a fixture
|
|
37
|
+
* matched (or would have) and `"proxy"` when the request was headed for the
|
|
38
|
+
* proxy path.
|
|
23
39
|
*/
|
|
24
|
-
declare function applyChaos(res: http$1.ServerResponse, fixture: Fixture | null, serverDefaults: ChaosConfig | undefined, rawHeaders: http$1.IncomingHttpHeaders, journal: Journal, context: ChaosJournalContext, registry?: MetricsRegistry, logger?: Logger): boolean;
|
|
25
40
|
//#endregion
|
|
26
41
|
export { applyChaos, evaluateChaos };
|
|
27
42
|
//# sourceMappingURL=chaos.d.cts.map
|
package/dist/chaos.d.cts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"chaos.d.cts","names":[],"sources":["../src/chaos.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;AA+HC;;AAKU,iBAhCK,aAAA,CAgCL,OAAA,EA/BA,OA+BA,GAAA,IAAA,EAAA,cAAA,CAAA,EA9BQ,WA8BR,EAAA,UAAA,CAAA,EA7BI,MAAA,CAAK,mBA6BT,EAAA,MAAA,CAAA,EA5BA,MA4BA,CAAA,EA3BR,WA2BQ,GAAA,IAAA;UAHD,mBAAA,CAIF;EAAqB,MAAA,EAAA,MAAA;
|
|
1
|
+
{"version":3,"file":"chaos.d.cts","names":[],"sources":["../src/chaos.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;AA+HC;;AAKU,iBAhCK,aAAA,CAgCL,OAAA,EA/BA,OA+BA,GAAA,IAAA,EAAA,cAAA,CAAA,EA9BQ,WA8BR,EAAA,UAAA,CAAA,EA7BI,MAAA,CAAK,mBA6BT,EAAA,MAAA,CAAA,EA5BA,MA4BA,CAAA,EA3BR,WA2BQ,GAAA,IAAA;UAHD,mBAAA,CAIF;EAAqB,MAAA,EAAA,MAAA;EAWb,IAAA,EAAA,MAAA;EAAU,OAAA,EAZf,MAYe,CAAA,MAAA,EAAA,MAAA,CAAA;MACnB,EAZC,qBAYI,GAAA,IAAA;;;;;;;;;;iBADI,UAAA,MACT,MAAA,CAAK,yBACD,gCACO,qCACJ,MAAA,CAAK,8BACR,kBACA,0EAEE,0BACF"}
|
package/dist/chaos.d.ts
CHANGED
|
@@ -15,13 +15,28 @@ interface ChaosJournalContext {
|
|
|
15
15
|
method: string;
|
|
16
16
|
path: string;
|
|
17
17
|
headers: Record<string, string>;
|
|
18
|
-
body: ChatCompletionRequest;
|
|
18
|
+
body: ChatCompletionRequest | null;
|
|
19
19
|
}
|
|
20
20
|
/**
|
|
21
21
|
* Apply chaos to a request. Returns true if chaos was applied (caller should
|
|
22
22
|
* return early), false if the request should proceed normally.
|
|
23
|
+
*
|
|
24
|
+
* `source` is required so the invariant "this handler only applies chaos in
|
|
25
|
+
* the <X> phase" is enforced at the type level. A future handler that grows
|
|
26
|
+
* a proxy path MUST pass `"proxy"` explicitly; the default can't drift silently.
|
|
27
|
+
*/
|
|
28
|
+
declare function applyChaos(res: http$1.ServerResponse, fixture: Fixture | null, serverDefaults: ChaosConfig | undefined, rawHeaders: http$1.IncomingHttpHeaders, journal: Journal, context: ChaosJournalContext, source: "fixture" | "proxy" | "internal", registry?: MetricsRegistry, logger?: Logger): boolean;
|
|
29
|
+
/**
|
|
30
|
+
* Apply a specific (already-rolled) chaos action. Exposed so callers that roll
|
|
31
|
+
* the dice themselves can dispatch without re-rolling — important when the
|
|
32
|
+
* caller wants to branch on the action before committing (e.g. pre-flight vs.
|
|
33
|
+
* post-response phases).
|
|
34
|
+
*
|
|
35
|
+
* `source` is required (not optional) so callers can't silently omit it on
|
|
36
|
+
* one branch and journal an ambiguous entry. Pass `"fixture"` when a fixture
|
|
37
|
+
* matched (or would have) and `"proxy"` when the request was headed for the
|
|
38
|
+
* proxy path.
|
|
23
39
|
*/
|
|
24
|
-
declare function applyChaos(res: http$1.ServerResponse, fixture: Fixture | null, serverDefaults: ChaosConfig | undefined, rawHeaders: http$1.IncomingHttpHeaders, journal: Journal, context: ChaosJournalContext, registry?: MetricsRegistry, logger?: Logger): boolean;
|
|
25
40
|
//#endregion
|
|
26
41
|
export { applyChaos, evaluateChaos };
|
|
27
42
|
//# sourceMappingURL=chaos.d.ts.map
|
package/dist/chaos.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"chaos.d.ts","names":[],"sources":["../src/chaos.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;AA+HC;;AAKU,iBAhCK,aAAA,CAgCL,OAAA,EA/BA,OA+BA,GAAA,IAAA,EAAA,cAAA,CAAA,EA9BQ,WA8BR,EAAA,UAAA,CAAA,EA7BI,MAAA,CAAK,mBA6BT,EAAA,MAAA,CAAA,EA5BA,MA4BA,CAAA,EA3BR,WA2BQ,GAAA,IAAA;UAHD,mBAAA,CAIF;EAAqB,MAAA,EAAA,MAAA;
|
|
1
|
+
{"version":3,"file":"chaos.d.ts","names":[],"sources":["../src/chaos.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;AA+HC;;AAKU,iBAhCK,aAAA,CAgCL,OAAA,EA/BA,OA+BA,GAAA,IAAA,EAAA,cAAA,CAAA,EA9BQ,WA8BR,EAAA,UAAA,CAAA,EA7BI,MAAA,CAAK,mBA6BT,EAAA,MAAA,CAAA,EA5BA,MA4BA,CAAA,EA3BR,WA2BQ,GAAA,IAAA;UAHD,mBAAA,CAIF;EAAqB,MAAA,EAAA,MAAA;EAWb,IAAA,EAAA,MAAA;EAAU,OAAA,EAZf,MAYe,CAAA,MAAA,EAAA,MAAA,CAAA;MACnB,EAZC,qBAYI,GAAA,IAAA;;;;;;;;;;iBADI,UAAA,MACT,MAAA,CAAK,yBACD,gCACO,qCACJ,MAAA,CAAK,8BACR,kBACA,0EAEE,0BACF"}
|
package/dist/chaos.js
CHANGED
|
@@ -60,11 +60,33 @@ function evaluateChaos(fixture, serverDefaults, rawHeaders, logger) {
|
|
|
60
60
|
/**
|
|
61
61
|
* Apply chaos to a request. Returns true if chaos was applied (caller should
|
|
62
62
|
* return early), false if the request should proceed normally.
|
|
63
|
+
*
|
|
64
|
+
* `source` is required so the invariant "this handler only applies chaos in
|
|
65
|
+
* the <X> phase" is enforced at the type level. A future handler that grows
|
|
66
|
+
* a proxy path MUST pass `"proxy"` explicitly; the default can't drift silently.
|
|
63
67
|
*/
|
|
64
|
-
function applyChaos(res, fixture, serverDefaults, rawHeaders, journal, context, registry, logger) {
|
|
68
|
+
function applyChaos(res, fixture, serverDefaults, rawHeaders, journal, context, source, registry, logger) {
|
|
65
69
|
const action = evaluateChaos(fixture, serverDefaults, rawHeaders, logger);
|
|
66
70
|
if (!action) return false;
|
|
67
|
-
|
|
71
|
+
applyChaosAction(action, res, fixture, journal, context, source, registry);
|
|
72
|
+
return true;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Apply a specific (already-rolled) chaos action. Exposed so callers that roll
|
|
76
|
+
* the dice themselves can dispatch without re-rolling — important when the
|
|
77
|
+
* caller wants to branch on the action before committing (e.g. pre-flight vs.
|
|
78
|
+
* post-response phases).
|
|
79
|
+
*
|
|
80
|
+
* `source` is required (not optional) so callers can't silently omit it on
|
|
81
|
+
* one branch and journal an ambiguous entry. Pass `"fixture"` when a fixture
|
|
82
|
+
* matched (or would have) and `"proxy"` when the request was headed for the
|
|
83
|
+
* proxy path.
|
|
84
|
+
*/
|
|
85
|
+
function applyChaosAction(action, res, fixture, journal, context, source, registry) {
|
|
86
|
+
if (registry) registry.incrementCounter("aimock_chaos_triggered_total", {
|
|
87
|
+
action,
|
|
88
|
+
source
|
|
89
|
+
});
|
|
68
90
|
switch (action) {
|
|
69
91
|
case "drop":
|
|
70
92
|
journal.add({
|
|
@@ -72,7 +94,8 @@ function applyChaos(res, fixture, serverDefaults, rawHeaders, journal, context,
|
|
|
72
94
|
response: {
|
|
73
95
|
status: 500,
|
|
74
96
|
fixture,
|
|
75
|
-
chaosAction: "drop"
|
|
97
|
+
chaosAction: "drop",
|
|
98
|
+
source
|
|
76
99
|
}
|
|
77
100
|
});
|
|
78
101
|
writeErrorResponse(res, 500, JSON.stringify({ error: {
|
|
@@ -80,34 +103,36 @@ function applyChaos(res, fixture, serverDefaults, rawHeaders, journal, context,
|
|
|
80
103
|
type: "server_error",
|
|
81
104
|
code: "chaos_drop"
|
|
82
105
|
} }));
|
|
83
|
-
return
|
|
106
|
+
return;
|
|
84
107
|
case "malformed":
|
|
85
108
|
journal.add({
|
|
86
109
|
...context,
|
|
87
110
|
response: {
|
|
88
111
|
status: 200,
|
|
89
112
|
fixture,
|
|
90
|
-
chaosAction: "malformed"
|
|
113
|
+
chaosAction: "malformed",
|
|
114
|
+
source
|
|
91
115
|
}
|
|
92
116
|
});
|
|
93
117
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
94
118
|
res.end("{malformed json: <<<chaos>>>");
|
|
95
|
-
return
|
|
119
|
+
return;
|
|
96
120
|
case "disconnect":
|
|
97
121
|
journal.add({
|
|
98
122
|
...context,
|
|
99
123
|
response: {
|
|
100
124
|
status: 0,
|
|
101
125
|
fixture,
|
|
102
|
-
chaosAction: "disconnect"
|
|
126
|
+
chaosAction: "disconnect",
|
|
127
|
+
source
|
|
103
128
|
}
|
|
104
129
|
});
|
|
105
130
|
res.destroy();
|
|
106
|
-
return
|
|
107
|
-
default: return
|
|
131
|
+
return;
|
|
132
|
+
default: return;
|
|
108
133
|
}
|
|
109
134
|
}
|
|
110
135
|
|
|
111
136
|
//#endregion
|
|
112
|
-
export { applyChaos, evaluateChaos };
|
|
137
|
+
export { applyChaos, applyChaosAction, evaluateChaos };
|
|
113
138
|
//# sourceMappingURL=chaos.js.map
|
package/dist/chaos.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"chaos.js","names":[],"sources":["../src/chaos.ts"],"sourcesContent":["/**\n * Chaos testing support for LLMock.\n *\n * Provides probabilistic failure injection — requests can be dropped (500),\n * returned with malformed JSON, or have the connection forcibly disconnected.\n *\n * Precedence: per-request headers > fixture-level config > server-level defaults.\n */\n\nimport type * as http from \"node:http\";\nimport type { ChaosAction, ChaosConfig, ChatCompletionRequest, Fixture } from \"./types.js\";\nimport { writeErrorResponse } from \"./sse-writer.js\";\nimport type { Journal } from \"./journal.js\";\nimport type { Logger } from \"./logger.js\";\nimport type { MetricsRegistry } from \"./metrics.js\";\n\n/**\n * Resolve chaos config from headers, fixture, and server defaults.\n * Header values override fixture values, which override server defaults.\n */\nfunction resolveChaosConfig(\n fixture: Fixture | null,\n serverDefaults?: ChaosConfig,\n rawHeaders?: http.IncomingHttpHeaders,\n logger?: Logger,\n): ChaosConfig {\n const base: ChaosConfig = { ...serverDefaults };\n\n // Fixture-level overrides server defaults\n if (fixture?.chaos) {\n if (fixture.chaos.dropRate !== undefined) base.dropRate = fixture.chaos.dropRate;\n if (fixture.chaos.malformedRate !== undefined) base.malformedRate = fixture.chaos.malformedRate;\n if (fixture.chaos.disconnectRate !== undefined)\n base.disconnectRate = fixture.chaos.disconnectRate;\n }\n\n // Header overrides everything\n if (rawHeaders) {\n const dropHeader = rawHeaders[\"x-aimock-chaos-drop\"];\n const malformedHeader = rawHeaders[\"x-aimock-chaos-malformed\"];\n const disconnectHeader = rawHeaders[\"x-aimock-chaos-disconnect\"];\n\n if (typeof dropHeader === \"string\") {\n const val = parseFloat(dropHeader);\n if (isNaN(val)) {\n logger?.warn(`[chaos] x-aimock-chaos-drop: invalid value \"${dropHeader}\", ignoring`);\n } else {\n if (val < 0 || val > 1) {\n logger?.warn(`[chaos] x-aimock-chaos-drop: value ${val} out of range [0,1], clamping`);\n }\n base.dropRate = Math.min(1, Math.max(0, val));\n }\n }\n if (typeof malformedHeader === \"string\") {\n const val = parseFloat(malformedHeader);\n if (isNaN(val)) {\n logger?.warn(\n `[chaos] x-aimock-chaos-malformed: invalid value \"${malformedHeader}\", ignoring`,\n );\n } else {\n if (val < 0 || val > 1) {\n logger?.warn(\n `[chaos] x-aimock-chaos-malformed: value ${val} out of range [0,1], clamping`,\n );\n }\n base.malformedRate = Math.min(1, Math.max(0, val));\n }\n }\n if (typeof disconnectHeader === \"string\") {\n const val = parseFloat(disconnectHeader);\n if (isNaN(val)) {\n logger?.warn(\n `[chaos] x-aimock-chaos-disconnect: invalid value \"${disconnectHeader}\", ignoring`,\n );\n } else {\n if (val < 0 || val > 1) {\n logger?.warn(\n `[chaos] x-aimock-chaos-disconnect: value ${val} out of range [0,1], clamping`,\n );\n }\n base.disconnectRate = Math.min(1, Math.max(0, val));\n }\n }\n }\n\n // Clamp all resolved rates to [0, 1] regardless of source.\n // Header values are already clamped above; this covers fixture-level and server defaults.\n if (base.dropRate !== undefined) base.dropRate = Math.min(1, Math.max(0, base.dropRate));\n if (base.malformedRate !== undefined)\n base.malformedRate = Math.min(1, Math.max(0, base.malformedRate));\n if (base.disconnectRate !== undefined)\n base.disconnectRate = Math.min(1, Math.max(0, base.disconnectRate));\n\n return base;\n}\n\n/**\n * Evaluate chaos config and return the triggered action, or null if none.\n * Checks in order: drop, malformed, disconnect — first hit wins.\n */\nexport function evaluateChaos(\n fixture: Fixture | null,\n serverDefaults?: ChaosConfig,\n rawHeaders?: http.IncomingHttpHeaders,\n logger?: Logger,\n): ChaosAction | null {\n const config = resolveChaosConfig(fixture, serverDefaults, rawHeaders, logger);\n\n if (config.dropRate !== undefined && config.dropRate > 0 && Math.random() < config.dropRate) {\n return \"drop\";\n }\n if (\n config.malformedRate !== undefined &&\n config.malformedRate > 0 &&\n Math.random() < config.malformedRate\n ) {\n return \"malformed\";\n }\n if (\n config.disconnectRate !== undefined &&\n config.disconnectRate > 0 &&\n Math.random() < config.disconnectRate\n ) {\n return \"disconnect\";\n }\n\n return null;\n}\n\ninterface ChaosJournalContext {\n method: string;\n path: string;\n headers: Record<string, string>;\n body: ChatCompletionRequest;\n}\n\n/**\n * Apply chaos to a request. Returns true if chaos was applied (caller should\n * return early), false if the request should proceed normally.\n */\nexport function applyChaos(\n res: http.ServerResponse,\n fixture: Fixture | null,\n serverDefaults: ChaosConfig | undefined,\n rawHeaders: http.IncomingHttpHeaders,\n journal: Journal,\n context: ChaosJournalContext,\n registry?: MetricsRegistry,\n logger?: Logger,\n): boolean {\n const action = evaluateChaos(fixture, serverDefaults, rawHeaders, logger);\n if (!action) return false;\n\n if (registry) {\n registry.incrementCounter(\"aimock_chaos_triggered_total\", { action });\n }\n\n switch (action) {\n case \"drop\": {\n journal.add({\n ...context,\n response: { status: 500, fixture, chaosAction: \"drop\" },\n });\n writeErrorResponse(\n res,\n 500,\n JSON.stringify({\n error: {\n message: \"Chaos: request dropped\",\n type: \"server_error\",\n code: \"chaos_drop\",\n },\n }),\n );\n return true;\n }\n case \"malformed\": {\n journal.add({\n ...context,\n response: { status: 200, fixture, chaosAction: \"malformed\" },\n });\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(\"{malformed json: <<<chaos>>>\");\n return true;\n }\n case \"disconnect\": {\n journal.add({\n ...context,\n response: { status: 0, fixture, chaosAction: \"disconnect\" },\n });\n res.destroy();\n return true;\n }\n default: {\n const _exhaustive: never = action;\n void _exhaustive;\n return false;\n }\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"chaos.js","names":[],"sources":["../src/chaos.ts"],"sourcesContent":["/**\n * Chaos testing support for LLMock.\n *\n * Provides probabilistic failure injection — requests can be dropped (500),\n * returned with malformed JSON, or have the connection forcibly disconnected.\n *\n * Precedence: per-request headers > fixture-level config > server-level defaults.\n */\n\nimport type * as http from \"node:http\";\nimport type { ChaosAction, ChaosConfig, ChatCompletionRequest, Fixture } from \"./types.js\";\nimport { writeErrorResponse } from \"./sse-writer.js\";\nimport type { Journal } from \"./journal.js\";\nimport type { Logger } from \"./logger.js\";\nimport type { MetricsRegistry } from \"./metrics.js\";\n\n/**\n * Resolve chaos config from headers, fixture, and server defaults.\n * Header values override fixture values, which override server defaults.\n */\nfunction resolveChaosConfig(\n fixture: Fixture | null,\n serverDefaults?: ChaosConfig,\n rawHeaders?: http.IncomingHttpHeaders,\n logger?: Logger,\n): ChaosConfig {\n const base: ChaosConfig = { ...serverDefaults };\n\n // Fixture-level overrides server defaults\n if (fixture?.chaos) {\n if (fixture.chaos.dropRate !== undefined) base.dropRate = fixture.chaos.dropRate;\n if (fixture.chaos.malformedRate !== undefined) base.malformedRate = fixture.chaos.malformedRate;\n if (fixture.chaos.disconnectRate !== undefined)\n base.disconnectRate = fixture.chaos.disconnectRate;\n }\n\n // Header overrides everything\n if (rawHeaders) {\n const dropHeader = rawHeaders[\"x-aimock-chaos-drop\"];\n const malformedHeader = rawHeaders[\"x-aimock-chaos-malformed\"];\n const disconnectHeader = rawHeaders[\"x-aimock-chaos-disconnect\"];\n\n if (typeof dropHeader === \"string\") {\n const val = parseFloat(dropHeader);\n if (isNaN(val)) {\n logger?.warn(`[chaos] x-aimock-chaos-drop: invalid value \"${dropHeader}\", ignoring`);\n } else {\n if (val < 0 || val > 1) {\n logger?.warn(`[chaos] x-aimock-chaos-drop: value ${val} out of range [0,1], clamping`);\n }\n base.dropRate = Math.min(1, Math.max(0, val));\n }\n }\n if (typeof malformedHeader === \"string\") {\n const val = parseFloat(malformedHeader);\n if (isNaN(val)) {\n logger?.warn(\n `[chaos] x-aimock-chaos-malformed: invalid value \"${malformedHeader}\", ignoring`,\n );\n } else {\n if (val < 0 || val > 1) {\n logger?.warn(\n `[chaos] x-aimock-chaos-malformed: value ${val} out of range [0,1], clamping`,\n );\n }\n base.malformedRate = Math.min(1, Math.max(0, val));\n }\n }\n if (typeof disconnectHeader === \"string\") {\n const val = parseFloat(disconnectHeader);\n if (isNaN(val)) {\n logger?.warn(\n `[chaos] x-aimock-chaos-disconnect: invalid value \"${disconnectHeader}\", ignoring`,\n );\n } else {\n if (val < 0 || val > 1) {\n logger?.warn(\n `[chaos] x-aimock-chaos-disconnect: value ${val} out of range [0,1], clamping`,\n );\n }\n base.disconnectRate = Math.min(1, Math.max(0, val));\n }\n }\n }\n\n // Clamp all resolved rates to [0, 1] regardless of source.\n // Header values are already clamped above; this covers fixture-level and server defaults.\n if (base.dropRate !== undefined) base.dropRate = Math.min(1, Math.max(0, base.dropRate));\n if (base.malformedRate !== undefined)\n base.malformedRate = Math.min(1, Math.max(0, base.malformedRate));\n if (base.disconnectRate !== undefined)\n base.disconnectRate = Math.min(1, Math.max(0, base.disconnectRate));\n\n return base;\n}\n\n/**\n * Evaluate chaos config and return the triggered action, or null if none.\n * Checks in order: drop, malformed, disconnect — first hit wins.\n */\nexport function evaluateChaos(\n fixture: Fixture | null,\n serverDefaults?: ChaosConfig,\n rawHeaders?: http.IncomingHttpHeaders,\n logger?: Logger,\n): ChaosAction | null {\n const config = resolveChaosConfig(fixture, serverDefaults, rawHeaders, logger);\n\n if (config.dropRate !== undefined && config.dropRate > 0 && Math.random() < config.dropRate) {\n return \"drop\";\n }\n if (\n config.malformedRate !== undefined &&\n config.malformedRate > 0 &&\n Math.random() < config.malformedRate\n ) {\n return \"malformed\";\n }\n if (\n config.disconnectRate !== undefined &&\n config.disconnectRate > 0 &&\n Math.random() < config.disconnectRate\n ) {\n return \"disconnect\";\n }\n\n return null;\n}\n\ninterface ChaosJournalContext {\n method: string;\n path: string;\n headers: Record<string, string>;\n body: ChatCompletionRequest | null;\n}\n\n/**\n * Apply chaos to a request. Returns true if chaos was applied (caller should\n * return early), false if the request should proceed normally.\n *\n * `source` is required so the invariant \"this handler only applies chaos in\n * the <X> phase\" is enforced at the type level. A future handler that grows\n * a proxy path MUST pass `\"proxy\"` explicitly; the default can't drift silently.\n */\nexport function applyChaos(\n res: http.ServerResponse,\n fixture: Fixture | null,\n serverDefaults: ChaosConfig | undefined,\n rawHeaders: http.IncomingHttpHeaders,\n journal: Journal,\n context: ChaosJournalContext,\n source: \"fixture\" | \"proxy\" | \"internal\",\n registry?: MetricsRegistry,\n logger?: Logger,\n): boolean {\n const action = evaluateChaos(fixture, serverDefaults, rawHeaders, logger);\n if (!action) return false;\n applyChaosAction(action, res, fixture, journal, context, source, registry);\n return true;\n}\n\n/**\n * Apply a specific (already-rolled) chaos action. Exposed so callers that roll\n * the dice themselves can dispatch without re-rolling — important when the\n * caller wants to branch on the action before committing (e.g. pre-flight vs.\n * post-response phases).\n *\n * `source` is required (not optional) so callers can't silently omit it on\n * one branch and journal an ambiguous entry. Pass `\"fixture\"` when a fixture\n * matched (or would have) and `\"proxy\"` when the request was headed for the\n * proxy path.\n */\nexport function applyChaosAction(\n action: ChaosAction,\n res: http.ServerResponse,\n fixture: Fixture | null,\n journal: Journal,\n context: ChaosJournalContext,\n source: \"fixture\" | \"proxy\" | \"internal\",\n registry?: MetricsRegistry,\n): void {\n if (registry) {\n registry.incrementCounter(\"aimock_chaos_triggered_total\", { action, source });\n }\n\n switch (action) {\n case \"drop\": {\n journal.add({\n ...context,\n response: { status: 500, fixture, chaosAction: \"drop\", source },\n });\n writeErrorResponse(\n res,\n 500,\n JSON.stringify({\n error: {\n message: \"Chaos: request dropped\",\n type: \"server_error\",\n code: \"chaos_drop\",\n },\n }),\n );\n return;\n }\n case \"malformed\": {\n journal.add({\n ...context,\n response: { status: 200, fixture, chaosAction: \"malformed\", source },\n });\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(\"{malformed json: <<<chaos>>>\");\n return;\n }\n case \"disconnect\": {\n journal.add({\n ...context,\n response: { status: 0, fixture, chaosAction: \"disconnect\", source },\n });\n res.destroy();\n return;\n }\n default: {\n const _exhaustive: never = action;\n void _exhaustive;\n return;\n }\n }\n}\n"],"mappings":"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"}
|
package/dist/cohere.cjs
CHANGED
|
@@ -517,10 +517,10 @@ async function handleCohere(req, res, raw, fixtures, journal, defaults, setCorsH
|
|
|
517
517
|
path: req.url ?? "/v2/chat",
|
|
518
518
|
headers: require_helpers.flattenHeaders(req.headers),
|
|
519
519
|
body: completionReq
|
|
520
|
-
}, defaults.registry, defaults.logger)) return;
|
|
520
|
+
}, fixture ? "fixture" : "proxy", defaults.registry, defaults.logger)) return;
|
|
521
521
|
if (!fixture) {
|
|
522
522
|
if (defaults.record) {
|
|
523
|
-
if (await require_recorder.proxyAndRecord(req, res, completionReq, "cohere", req.url ?? "/v2/chat", fixtures, defaults, raw)) {
|
|
523
|
+
if (await require_recorder.proxyAndRecord(req, res, completionReq, "cohere", req.url ?? "/v2/chat", fixtures, defaults, raw) !== "not_configured") {
|
|
524
524
|
journal.add({
|
|
525
525
|
method: req.method ?? "POST",
|
|
526
526
|
path: req.url ?? "/v2/chat",
|
package/dist/cohere.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cohere.cjs","names":["generateToolCallId","generateMessageId","calculateDelay","delay","flattenHeaders","getTestId","matchFixture","applyChaos","proxyAndRecord","isErrorResponse","isContentWithToolCallsResponse","extractOverrides","createInterruptionSignal","isTextResponse","isToolCallResponse"],"sources":["../src/cohere.ts"],"sourcesContent":["/**\n * Cohere v2 Chat API endpoint support.\n *\n * Translates incoming /v2/chat requests into the ChatCompletionRequest\n * format used by the fixture router, and converts fixture responses back into\n * Cohere's typed SSE streaming (or non-streaming) format.\n *\n * Cohere uses typed SSE events (event: + data: lines), similar to the\n * Claude Messages handler in messages.ts.\n */\n\nimport type * as http from \"node:http\";\nimport type {\n ChatCompletionRequest,\n ChatMessage,\n Fixture,\n HandlerDefaults,\n ResponseOverrides,\n StreamingProfile,\n ToolCall,\n ToolDefinition,\n} from \"./types.js\";\nimport {\n generateMessageId,\n generateToolCallId,\n extractOverrides,\n isTextResponse,\n isToolCallResponse,\n isContentWithToolCallsResponse,\n isErrorResponse,\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// ─── Cohere v2 Chat request types ───────────────────────────────────────────\n\ninterface CohereToolCallDef {\n id?: string;\n type: string;\n function: {\n name: string;\n arguments: string;\n };\n}\n\ninterface CohereMessage {\n role: \"user\" | \"assistant\" | \"system\" | \"tool\";\n content: string;\n tool_call_id?: string;\n tool_calls?: CohereToolCallDef[];\n}\n\ninterface CohereToolDef {\n type: string;\n function: {\n name: string;\n description?: string;\n parameters?: object;\n };\n}\n\ninterface CohereRequest {\n model: string;\n messages: CohereMessage[];\n stream?: boolean;\n tools?: CohereToolDef[];\n response_format?: { type: string; json_schema?: object };\n}\n\n// ─── Cohere SSE event types ─────────────────────────────────────────────────\n\ninterface CohereSSEEvent {\n type: string;\n [key: string]: unknown;\n}\n\n// ─── Zero-value usage block ─────────────────────────────────────────────────\n\nconst ZERO_USAGE = {\n billed_units: { input_tokens: 0, output_tokens: 0, search_units: 0, classifications: 0 },\n tokens: { input_tokens: 0, output_tokens: 0 },\n};\n\n// ─── Cohere finish reason / usage mapping ──────────────────────────────────\n\nfunction cohereFinishReason(\n overrideFinishReason: string | undefined,\n defaultReason: string,\n): string {\n if (!overrideFinishReason) return defaultReason;\n if (overrideFinishReason === \"stop\") return \"COMPLETE\";\n if (overrideFinishReason === \"tool_calls\") return \"TOOL_CALL\";\n if (overrideFinishReason === \"length\") return \"MAX_TOKENS\";\n return overrideFinishReason;\n}\n\nfunction cohereUsage(overrides?: ResponseOverrides): typeof ZERO_USAGE {\n if (!overrides?.usage) return ZERO_USAGE;\n const inputTokens = overrides.usage.input_tokens ?? overrides.usage.prompt_tokens ?? 0;\n const outputTokens = overrides.usage.output_tokens ?? overrides.usage.completion_tokens ?? 0;\n return {\n billed_units: {\n input_tokens: inputTokens,\n output_tokens: outputTokens,\n search_units: 0,\n classifications: 0,\n },\n tokens: { input_tokens: inputTokens, output_tokens: outputTokens },\n };\n}\n\n// ─── Input conversion: Cohere → ChatCompletionRequest ───────────────────────\n\nexport function cohereToCompletionRequest(req: CohereRequest): ChatCompletionRequest {\n const messages: ChatMessage[] = [];\n\n for (const msg of req.messages) {\n if (msg.role === \"system\") {\n messages.push({ role: \"system\", content: msg.content });\n } else if (msg.role === \"user\") {\n messages.push({ role: \"user\", content: msg.content });\n } else if (msg.role === \"assistant\") {\n if (msg.tool_calls && msg.tool_calls.length > 0) {\n messages.push({\n role: \"assistant\",\n content: msg.content || null,\n tool_calls: msg.tool_calls.map((tc) => ({\n id: tc.id ?? generateToolCallId(),\n type: \"function\" as const,\n function: {\n name: tc.function.name,\n arguments: tc.function.arguments,\n },\n })),\n });\n } else {\n messages.push({ role: \"assistant\", content: msg.content });\n }\n } else if (msg.role === \"tool\") {\n messages.push({\n role: \"tool\",\n content: msg.content,\n tool_call_id: msg.tool_call_id,\n });\n }\n }\n\n // Convert tools\n let tools: ToolDefinition[] | undefined;\n if (req.tools && req.tools.length > 0) {\n tools = req.tools.map((t) => ({\n type: \"function\" as const,\n function: {\n name: t.function.name,\n description: t.function.description,\n parameters: t.function.parameters,\n },\n }));\n }\n\n return {\n model: req.model,\n messages,\n stream: req.stream,\n tools,\n ...(req.response_format && { response_format: req.response_format }),\n };\n}\n\n// ─── Response building: fixture → Cohere v2 Chat format ─────────────────────\n\n// Non-streaming text response\nfunction buildCohereTextResponse(\n content: string,\n reasoning?: string,\n overrides?: ResponseOverrides,\n): object {\n const contentBlocks: { type: string; text: string }[] = [];\n if (reasoning) {\n contentBlocks.push({ type: \"text\", text: reasoning });\n }\n contentBlocks.push({ type: \"text\", text: content });\n\n return {\n id: overrides?.id ?? generateMessageId(),\n finish_reason: cohereFinishReason(overrides?.finishReason, \"COMPLETE\"),\n message: {\n role: \"assistant\",\n content: contentBlocks,\n tool_calls: [],\n tool_plan: \"\",\n citations: [],\n },\n usage: cohereUsage(overrides),\n };\n}\n\n// Non-streaming tool call response\nfunction buildCohereToolCallResponse(\n toolCalls: ToolCall[],\n logger: Logger,\n overrides?: ResponseOverrides,\n): object {\n const cohereCalls = toolCalls.map((tc) => {\n // Validate arguments JSON\n let argsJson: string;\n try {\n JSON.parse(tc.arguments || \"{}\");\n argsJson = tc.arguments || \"{}\";\n } catch {\n logger.warn(\n `Malformed JSON in fixture tool call arguments for \"${tc.name}\": ${tc.arguments}`,\n );\n argsJson = \"{}\";\n }\n return {\n id: tc.id || generateToolCallId(),\n type: \"function\",\n function: {\n name: tc.name,\n arguments: argsJson,\n },\n };\n });\n\n return {\n id: overrides?.id ?? generateMessageId(),\n finish_reason: cohereFinishReason(overrides?.finishReason, \"TOOL_CALL\"),\n message: {\n role: \"assistant\",\n content: [],\n tool_calls: cohereCalls,\n tool_plan: \"\",\n citations: [],\n },\n usage: cohereUsage(overrides),\n };\n}\n\n// Non-streaming content + tool calls response\nfunction buildCohereContentWithToolCallsResponse(\n content: string,\n toolCalls: ToolCall[],\n logger: Logger,\n reasoning?: string,\n overrides?: ResponseOverrides,\n): object {\n const cohereCalls = toolCalls.map((tc) => {\n let argsJson: string;\n try {\n JSON.parse(tc.arguments || \"{}\");\n argsJson = tc.arguments || \"{}\";\n } catch {\n logger.warn(\n `Malformed JSON in fixture tool call arguments for \"${tc.name}\": ${tc.arguments}`,\n );\n argsJson = \"{}\";\n }\n return {\n id: tc.id || generateToolCallId(),\n type: \"function\",\n function: {\n name: tc.name,\n arguments: argsJson,\n },\n };\n });\n\n const contentBlocks: { type: string; text: string }[] = [];\n if (reasoning) {\n contentBlocks.push({ type: \"text\", text: reasoning });\n }\n contentBlocks.push({ type: \"text\", text: content });\n\n return {\n id: overrides?.id ?? generateMessageId(),\n finish_reason: cohereFinishReason(overrides?.finishReason, \"TOOL_CALL\"),\n message: {\n role: \"assistant\",\n content: contentBlocks,\n tool_calls: cohereCalls,\n tool_plan: \"\",\n citations: [],\n },\n usage: cohereUsage(overrides),\n };\n}\n\n// ─── Streaming event builders ───────────────────────────────────────────────\n\nfunction buildCohereTextStreamEvents(\n content: string,\n chunkSize: number,\n reasoning?: string,\n overrides?: ResponseOverrides,\n): CohereSSEEvent[] {\n const msgId = overrides?.id ?? generateMessageId();\n const events: CohereSSEEvent[] = [];\n\n // message-start\n events.push({\n id: msgId,\n type: \"message-start\",\n delta: {\n message: {\n role: \"assistant\",\n content: [],\n tool_plan: \"\",\n tool_calls: [],\n citations: [],\n },\n },\n });\n\n let contentIndex = 0;\n\n // Reasoning as a text block before main content (Cohere has no native reasoning type)\n if (reasoning) {\n events.push({\n type: \"content-start\",\n index: contentIndex,\n delta: { message: { content: { type: \"text\" } } },\n });\n for (let i = 0; i < reasoning.length; i += chunkSize) {\n const slice = reasoning.slice(i, i + chunkSize);\n events.push({\n type: \"content-delta\",\n index: contentIndex,\n delta: { message: { content: { type: \"text\", text: slice } } },\n });\n }\n events.push({ type: \"content-end\", index: contentIndex });\n contentIndex++;\n }\n\n // content-start (type: \"text\" only, no text field)\n events.push({\n type: \"content-start\",\n index: contentIndex,\n delta: {\n message: {\n content: { type: \"text\" },\n },\n },\n });\n\n // content-delta — text chunks\n for (let i = 0; i < content.length; i += chunkSize) {\n const slice = content.slice(i, i + chunkSize);\n events.push({\n type: \"content-delta\",\n index: contentIndex,\n delta: {\n message: {\n content: { type: \"text\", text: slice },\n },\n },\n });\n }\n\n // content-end\n events.push({\n type: \"content-end\",\n index: contentIndex,\n });\n\n // message-end\n events.push({\n type: \"message-end\",\n delta: {\n finish_reason: cohereFinishReason(overrides?.finishReason, \"COMPLETE\"),\n usage: cohereUsage(overrides),\n },\n });\n\n return events;\n}\n\nfunction buildCohereToolCallStreamEvents(\n toolCalls: ToolCall[],\n chunkSize: number,\n logger: Logger,\n overrides?: ResponseOverrides,\n): CohereSSEEvent[] {\n const msgId = overrides?.id ?? generateMessageId();\n const events: CohereSSEEvent[] = [];\n\n // message-start\n events.push({\n id: msgId,\n type: \"message-start\",\n delta: {\n message: {\n role: \"assistant\",\n content: [],\n tool_plan: \"\",\n tool_calls: [],\n citations: [],\n },\n },\n });\n\n // tool-plan-delta\n events.push({\n type: \"tool-plan-delta\",\n delta: {\n message: {\n tool_plan: \"I will use the requested tool.\",\n },\n },\n });\n\n for (let idx = 0; idx < toolCalls.length; idx++) {\n const tc = toolCalls[idx];\n const callId = tc.id || generateToolCallId();\n\n // Validate arguments JSON\n let argsJson: string;\n try {\n JSON.parse(tc.arguments || \"{}\");\n argsJson = tc.arguments || \"{}\";\n } catch {\n logger.warn(\n `Malformed JSON in fixture tool call arguments for \"${tc.name}\": ${tc.arguments}`,\n );\n argsJson = \"{}\";\n }\n\n // tool-call-start\n events.push({\n type: \"tool-call-start\",\n index: idx,\n delta: {\n message: {\n tool_calls: {\n id: callId,\n type: \"function\",\n function: {\n name: tc.name,\n arguments: \"\",\n },\n },\n },\n },\n });\n\n // tool-call-delta — chunked arguments\n for (let i = 0; i < argsJson.length; i += chunkSize) {\n const slice = argsJson.slice(i, i + chunkSize);\n events.push({\n type: \"tool-call-delta\",\n index: idx,\n delta: {\n message: {\n tool_calls: {\n function: {\n arguments: slice,\n },\n },\n },\n },\n });\n }\n\n // tool-call-end\n events.push({\n type: \"tool-call-end\",\n index: idx,\n });\n }\n\n // message-end\n events.push({\n type: \"message-end\",\n delta: {\n finish_reason: cohereFinishReason(overrides?.finishReason, \"TOOL_CALL\"),\n usage: cohereUsage(overrides),\n },\n });\n\n return events;\n}\n\nfunction buildCohereContentWithToolCallsStreamEvents(\n content: string,\n toolCalls: ToolCall[],\n chunkSize: number,\n logger: Logger,\n reasoning?: string,\n overrides?: ResponseOverrides,\n): CohereSSEEvent[] {\n const msgId = overrides?.id ?? generateMessageId();\n const events: CohereSSEEvent[] = [];\n\n // message-start\n events.push({\n id: msgId,\n type: \"message-start\",\n delta: {\n message: {\n role: \"assistant\",\n content: [],\n tool_plan: \"\",\n tool_calls: [],\n citations: [],\n },\n },\n });\n\n let contentIndex = 0;\n\n // Reasoning as a text block before main content\n if (reasoning) {\n events.push({\n type: \"content-start\",\n index: contentIndex,\n delta: { message: { content: { type: \"text\" } } },\n });\n for (let i = 0; i < reasoning.length; i += chunkSize) {\n const slice = reasoning.slice(i, i + chunkSize);\n events.push({\n type: \"content-delta\",\n index: contentIndex,\n delta: { message: { content: { type: \"text\", text: slice } } },\n });\n }\n events.push({ type: \"content-end\", index: contentIndex });\n contentIndex++;\n }\n\n // content-start (type: \"text\" only, no text field)\n events.push({\n type: \"content-start\",\n index: contentIndex,\n delta: {\n message: {\n content: { type: \"text\" },\n },\n },\n });\n\n // content-delta — text chunks\n for (let i = 0; i < content.length; i += chunkSize) {\n const slice = content.slice(i, i + chunkSize);\n events.push({\n type: \"content-delta\",\n index: contentIndex,\n delta: {\n message: {\n content: { type: \"text\", text: slice },\n },\n },\n });\n }\n\n // content-end\n events.push({\n type: \"content-end\",\n index: contentIndex,\n });\n\n // tool-plan-delta\n events.push({\n type: \"tool-plan-delta\",\n delta: {\n message: {\n tool_plan: \"I will use the requested tool.\",\n },\n },\n });\n\n // Tool call events\n for (let idx = 0; idx < toolCalls.length; idx++) {\n const tc = toolCalls[idx];\n const callId = tc.id || generateToolCallId();\n\n let argsJson: string;\n try {\n JSON.parse(tc.arguments || \"{}\");\n argsJson = tc.arguments || \"{}\";\n } catch {\n logger.warn(\n `Malformed JSON in fixture tool call arguments for \"${tc.name}\": ${tc.arguments}`,\n );\n argsJson = \"{}\";\n }\n\n // tool-call-start\n events.push({\n type: \"tool-call-start\",\n index: idx,\n delta: {\n message: {\n tool_calls: {\n id: callId,\n type: \"function\",\n function: {\n name: tc.name,\n arguments: \"\",\n },\n },\n },\n },\n });\n\n // tool-call-delta — chunked arguments\n for (let i = 0; i < argsJson.length; i += chunkSize) {\n const slice = argsJson.slice(i, i + chunkSize);\n events.push({\n type: \"tool-call-delta\",\n index: idx,\n delta: {\n message: {\n tool_calls: {\n function: {\n arguments: slice,\n },\n },\n },\n },\n });\n }\n\n // tool-call-end\n events.push({\n type: \"tool-call-end\",\n index: idx,\n });\n }\n\n // message-end\n events.push({\n type: \"message-end\",\n delta: {\n finish_reason: cohereFinishReason(overrides?.finishReason, \"TOOL_CALL\"),\n usage: cohereUsage(overrides),\n },\n });\n\n return events;\n}\n\n// ─── SSE writer for Cohere typed events ─────────────────────────────────────\n\ninterface CohereStreamOptions {\n latency?: number;\n streamingProfile?: StreamingProfile;\n signal?: AbortSignal;\n onChunkSent?: () => void;\n}\n\nasync function writeCohereSSEStream(\n res: http.ServerResponse,\n events: CohereSSEEvent[],\n optionsOrLatency?: number | CohereStreamOptions,\n): Promise<boolean> {\n const opts: CohereStreamOptions =\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 event of events) {\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 res.write(`event: ${event.type}\\ndata: ${JSON.stringify(event)}\\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 handleCohere(\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 cohereReq: CohereRequest;\n try {\n cohereReq = JSON.parse(raw) as CohereRequest;\n } catch {\n journal.add({\n method: req.method ?? \"POST\",\n path: req.url ?? \"/v2/chat\",\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 },\n }),\n );\n return;\n }\n\n // Validate required model field\n if (!cohereReq.model) {\n journal.add({\n method: req.method ?? \"POST\",\n path: req.url ?? \"/v2/chat\",\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: \"model is required\",\n type: \"invalid_request_error\",\n },\n }),\n );\n return;\n }\n\n if (!cohereReq.messages || !Array.isArray(cohereReq.messages)) {\n journal.add({\n method: req.method ?? \"POST\",\n path: req.url ?? \"/v2/chat\",\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: \"Invalid request: messages array is required\",\n type: \"invalid_request_error\",\n },\n }),\n );\n return;\n }\n\n // Convert to ChatCompletionRequest for fixture matching\n const completionReq = cohereToCompletionRequest(cohereReq);\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\n if (fixture) {\n journal.incrementFixtureMatchCount(fixture, fixtures, testId);\n logger.debug(`Fixture matched: ${JSON.stringify(fixture.match).slice(0, 120)}`);\n } else {\n logger.debug(`No fixture matched for request`);\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 ?? \"/v2/chat\",\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 \"cohere\",\n req.url ?? \"/v2/chat\",\n fixtures,\n defaults,\n raw,\n );\n if (proxied) {\n journal.add({\n method: req.method ?? \"POST\",\n path: req.url ?? \"/v2/chat\",\n headers: flattenHeaders(req.headers),\n body: completionReq,\n response: { status: res.statusCode ?? 200, fixture: null, source: \"proxy\" },\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(\n `STRICT: No fixture matched for ${req.method ?? \"POST\"} ${req.url ?? \"/v2/chat\"}`,\n );\n }\n journal.add({\n method: req.method ?? \"POST\",\n path: req.url ?? \"/v2/chat\",\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 type: \"invalid_request_error\",\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: req.url ?? \"/v2/chat\",\n headers: flattenHeaders(req.headers),\n body: completionReq,\n response: { status, fixture },\n });\n writeErrorResponse(res, status, JSON.stringify(response));\n return;\n }\n\n // Content + tool calls response (must be checked before text/tool-only branches)\n if (isContentWithToolCallsResponse(response)) {\n if (response.webSearches?.length) {\n logger.warn(\n \"webSearches in fixture response are not supported for Cohere v2 Chat API — ignoring\",\n );\n }\n const overrides = extractOverrides(response);\n const journalEntry = journal.add({\n method: req.method ?? \"POST\",\n path: req.url ?? \"/v2/chat\",\n headers: flattenHeaders(req.headers),\n body: completionReq,\n response: { status: 200, fixture },\n });\n if (cohereReq.stream !== true) {\n const body = buildCohereContentWithToolCallsResponse(\n response.content,\n response.toolCalls,\n logger,\n response.reasoning,\n overrides,\n );\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify(body));\n } else {\n const events = buildCohereContentWithToolCallsStreamEvents(\n response.content,\n response.toolCalls,\n chunkSize,\n logger,\n response.reasoning,\n overrides,\n );\n const interruption = createInterruptionSignal(fixture);\n const completed = await writeCohereSSEStream(res, events, {\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 if (response.webSearches?.length) {\n logger.warn(\n \"webSearches in fixture response are not supported for Cohere v2 Chat API — ignoring\",\n );\n }\n const overrides = extractOverrides(response);\n const journalEntry = journal.add({\n method: req.method ?? \"POST\",\n path: req.url ?? \"/v2/chat\",\n headers: flattenHeaders(req.headers),\n body: completionReq,\n response: { status: 200, fixture },\n });\n if (cohereReq.stream !== true) {\n const body = buildCohereTextResponse(response.content, response.reasoning, overrides);\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify(body));\n } else {\n const events = buildCohereTextStreamEvents(\n response.content,\n chunkSize,\n response.reasoning,\n overrides,\n );\n const interruption = createInterruptionSignal(fixture);\n const completed = await writeCohereSSEStream(res, events, {\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 overrides = extractOverrides(response);\n const journalEntry = journal.add({\n method: req.method ?? \"POST\",\n path: req.url ?? \"/v2/chat\",\n headers: flattenHeaders(req.headers),\n body: completionReq,\n response: { status: 200, fixture },\n });\n if (cohereReq.stream !== true) {\n const body = buildCohereToolCallResponse(response.toolCalls, logger, overrides);\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify(body));\n } else {\n const events = buildCohereToolCallStreamEvents(\n response.toolCalls,\n chunkSize,\n logger,\n overrides,\n );\n const interruption = createInterruptionSignal(fixture);\n const completed = await writeCohereSSEStream(res, events, {\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: req.url ?? \"/v2/chat\",\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 type: \"server_error\",\n },\n }),\n );\n}\n"],"mappings":";;;;;;;;AAqFA,MAAM,aAAa;CACjB,cAAc;EAAE,cAAc;EAAG,eAAe;EAAG,cAAc;EAAG,iBAAiB;EAAG;CACxF,QAAQ;EAAE,cAAc;EAAG,eAAe;EAAG;CAC9C;AAID,SAAS,mBACP,sBACA,eACQ;AACR,KAAI,CAAC,qBAAsB,QAAO;AAClC,KAAI,yBAAyB,OAAQ,QAAO;AAC5C,KAAI,yBAAyB,aAAc,QAAO;AAClD,KAAI,yBAAyB,SAAU,QAAO;AAC9C,QAAO;;AAGT,SAAS,YAAY,WAAkD;AACrE,KAAI,CAAC,WAAW,MAAO,QAAO;CAC9B,MAAM,cAAc,UAAU,MAAM,gBAAgB,UAAU,MAAM,iBAAiB;CACrF,MAAM,eAAe,UAAU,MAAM,iBAAiB,UAAU,MAAM,qBAAqB;AAC3F,QAAO;EACL,cAAc;GACZ,cAAc;GACd,eAAe;GACf,cAAc;GACd,iBAAiB;GAClB;EACD,QAAQ;GAAE,cAAc;GAAa,eAAe;GAAc;EACnE;;AAKH,SAAgB,0BAA0B,KAA2C;CACnF,MAAM,WAA0B,EAAE;AAElC,MAAK,MAAM,OAAO,IAAI,SACpB,KAAI,IAAI,SAAS,SACf,UAAS,KAAK;EAAE,MAAM;EAAU,SAAS,IAAI;EAAS,CAAC;UAC9C,IAAI,SAAS,OACtB,UAAS,KAAK;EAAE,MAAM;EAAQ,SAAS,IAAI;EAAS,CAAC;UAC5C,IAAI,SAAS,YACtB,KAAI,IAAI,cAAc,IAAI,WAAW,SAAS,EAC5C,UAAS,KAAK;EACZ,MAAM;EACN,SAAS,IAAI,WAAW;EACxB,YAAY,IAAI,WAAW,KAAK,QAAQ;GACtC,IAAI,GAAG,MAAMA,oCAAoB;GACjC,MAAM;GACN,UAAU;IACR,MAAM,GAAG,SAAS;IAClB,WAAW,GAAG,SAAS;IACxB;GACF,EAAE;EACJ,CAAC;KAEF,UAAS,KAAK;EAAE,MAAM;EAAa,SAAS,IAAI;EAAS,CAAC;UAEnD,IAAI,SAAS,OACtB,UAAS,KAAK;EACZ,MAAM;EACN,SAAS,IAAI;EACb,cAAc,IAAI;EACnB,CAAC;CAKN,IAAI;AACJ,KAAI,IAAI,SAAS,IAAI,MAAM,SAAS,EAClC,SAAQ,IAAI,MAAM,KAAK,OAAO;EAC5B,MAAM;EACN,UAAU;GACR,MAAM,EAAE,SAAS;GACjB,aAAa,EAAE,SAAS;GACxB,YAAY,EAAE,SAAS;GACxB;EACF,EAAE;AAGL,QAAO;EACL,OAAO,IAAI;EACX;EACA,QAAQ,IAAI;EACZ;EACA,GAAI,IAAI,mBAAmB,EAAE,iBAAiB,IAAI,iBAAiB;EACpE;;AAMH,SAAS,wBACP,SACA,WACA,WACQ;CACR,MAAM,gBAAkD,EAAE;AAC1D,KAAI,UACF,eAAc,KAAK;EAAE,MAAM;EAAQ,MAAM;EAAW,CAAC;AAEvD,eAAc,KAAK;EAAE,MAAM;EAAQ,MAAM;EAAS,CAAC;AAEnD,QAAO;EACL,IAAI,WAAW,MAAMC,mCAAmB;EACxC,eAAe,mBAAmB,WAAW,cAAc,WAAW;EACtE,SAAS;GACP,MAAM;GACN,SAAS;GACT,YAAY,EAAE;GACd,WAAW;GACX,WAAW,EAAE;GACd;EACD,OAAO,YAAY,UAAU;EAC9B;;AAIH,SAAS,4BACP,WACA,QACA,WACQ;CACR,MAAM,cAAc,UAAU,KAAK,OAAO;EAExC,IAAI;AACJ,MAAI;AACF,QAAK,MAAM,GAAG,aAAa,KAAK;AAChC,cAAW,GAAG,aAAa;UACrB;AACN,UAAO,KACL,sDAAsD,GAAG,KAAK,KAAK,GAAG,YACvE;AACD,cAAW;;AAEb,SAAO;GACL,IAAI,GAAG,MAAMD,oCAAoB;GACjC,MAAM;GACN,UAAU;IACR,MAAM,GAAG;IACT,WAAW;IACZ;GACF;GACD;AAEF,QAAO;EACL,IAAI,WAAW,MAAMC,mCAAmB;EACxC,eAAe,mBAAmB,WAAW,cAAc,YAAY;EACvE,SAAS;GACP,MAAM;GACN,SAAS,EAAE;GACX,YAAY;GACZ,WAAW;GACX,WAAW,EAAE;GACd;EACD,OAAO,YAAY,UAAU;EAC9B;;AAIH,SAAS,wCACP,SACA,WACA,QACA,WACA,WACQ;CACR,MAAM,cAAc,UAAU,KAAK,OAAO;EACxC,IAAI;AACJ,MAAI;AACF,QAAK,MAAM,GAAG,aAAa,KAAK;AAChC,cAAW,GAAG,aAAa;UACrB;AACN,UAAO,KACL,sDAAsD,GAAG,KAAK,KAAK,GAAG,YACvE;AACD,cAAW;;AAEb,SAAO;GACL,IAAI,GAAG,MAAMD,oCAAoB;GACjC,MAAM;GACN,UAAU;IACR,MAAM,GAAG;IACT,WAAW;IACZ;GACF;GACD;CAEF,MAAM,gBAAkD,EAAE;AAC1D,KAAI,UACF,eAAc,KAAK;EAAE,MAAM;EAAQ,MAAM;EAAW,CAAC;AAEvD,eAAc,KAAK;EAAE,MAAM;EAAQ,MAAM;EAAS,CAAC;AAEnD,QAAO;EACL,IAAI,WAAW,MAAMC,mCAAmB;EACxC,eAAe,mBAAmB,WAAW,cAAc,YAAY;EACvE,SAAS;GACP,MAAM;GACN,SAAS;GACT,YAAY;GACZ,WAAW;GACX,WAAW,EAAE;GACd;EACD,OAAO,YAAY,UAAU;EAC9B;;AAKH,SAAS,4BACP,SACA,WACA,WACA,WACkB;CAClB,MAAM,QAAQ,WAAW,MAAMA,mCAAmB;CAClD,MAAM,SAA2B,EAAE;AAGnC,QAAO,KAAK;EACV,IAAI;EACJ,MAAM;EACN,OAAO,EACL,SAAS;GACP,MAAM;GACN,SAAS,EAAE;GACX,WAAW;GACX,YAAY,EAAE;GACd,WAAW,EAAE;GACd,EACF;EACF,CAAC;CAEF,IAAI,eAAe;AAGnB,KAAI,WAAW;AACb,SAAO,KAAK;GACV,MAAM;GACN,OAAO;GACP,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,QAAQ,EAAE,EAAE;GAClD,CAAC;AACF,OAAK,IAAI,IAAI,GAAG,IAAI,UAAU,QAAQ,KAAK,WAAW;GACpD,MAAM,QAAQ,UAAU,MAAM,GAAG,IAAI,UAAU;AAC/C,UAAO,KAAK;IACV,MAAM;IACN,OAAO;IACP,OAAO,EAAE,SAAS,EAAE,SAAS;KAAE,MAAM;KAAQ,MAAM;KAAO,EAAE,EAAE;IAC/D,CAAC;;AAEJ,SAAO,KAAK;GAAE,MAAM;GAAe,OAAO;GAAc,CAAC;AACzD;;AAIF,QAAO,KAAK;EACV,MAAM;EACN,OAAO;EACP,OAAO,EACL,SAAS,EACP,SAAS,EAAE,MAAM,QAAQ,EAC1B,EACF;EACF,CAAC;AAGF,MAAK,IAAI,IAAI,GAAG,IAAI,QAAQ,QAAQ,KAAK,WAAW;EAClD,MAAM,QAAQ,QAAQ,MAAM,GAAG,IAAI,UAAU;AAC7C,SAAO,KAAK;GACV,MAAM;GACN,OAAO;GACP,OAAO,EACL,SAAS,EACP,SAAS;IAAE,MAAM;IAAQ,MAAM;IAAO,EACvC,EACF;GACF,CAAC;;AAIJ,QAAO,KAAK;EACV,MAAM;EACN,OAAO;EACR,CAAC;AAGF,QAAO,KAAK;EACV,MAAM;EACN,OAAO;GACL,eAAe,mBAAmB,WAAW,cAAc,WAAW;GACtE,OAAO,YAAY,UAAU;GAC9B;EACF,CAAC;AAEF,QAAO;;AAGT,SAAS,gCACP,WACA,WACA,QACA,WACkB;CAClB,MAAM,QAAQ,WAAW,MAAMA,mCAAmB;CAClD,MAAM,SAA2B,EAAE;AAGnC,QAAO,KAAK;EACV,IAAI;EACJ,MAAM;EACN,OAAO,EACL,SAAS;GACP,MAAM;GACN,SAAS,EAAE;GACX,WAAW;GACX,YAAY,EAAE;GACd,WAAW,EAAE;GACd,EACF;EACF,CAAC;AAGF,QAAO,KAAK;EACV,MAAM;EACN,OAAO,EACL,SAAS,EACP,WAAW,kCACZ,EACF;EACF,CAAC;AAEF,MAAK,IAAI,MAAM,GAAG,MAAM,UAAU,QAAQ,OAAO;EAC/C,MAAM,KAAK,UAAU;EACrB,MAAM,SAAS,GAAG,MAAMD,oCAAoB;EAG5C,IAAI;AACJ,MAAI;AACF,QAAK,MAAM,GAAG,aAAa,KAAK;AAChC,cAAW,GAAG,aAAa;UACrB;AACN,UAAO,KACL,sDAAsD,GAAG,KAAK,KAAK,GAAG,YACvE;AACD,cAAW;;AAIb,SAAO,KAAK;GACV,MAAM;GACN,OAAO;GACP,OAAO,EACL,SAAS,EACP,YAAY;IACV,IAAI;IACJ,MAAM;IACN,UAAU;KACR,MAAM,GAAG;KACT,WAAW;KACZ;IACF,EACF,EACF;GACF,CAAC;AAGF,OAAK,IAAI,IAAI,GAAG,IAAI,SAAS,QAAQ,KAAK,WAAW;GACnD,MAAM,QAAQ,SAAS,MAAM,GAAG,IAAI,UAAU;AAC9C,UAAO,KAAK;IACV,MAAM;IACN,OAAO;IACP,OAAO,EACL,SAAS,EACP,YAAY,EACV,UAAU,EACR,WAAW,OACZ,EACF,EACF,EACF;IACF,CAAC;;AAIJ,SAAO,KAAK;GACV,MAAM;GACN,OAAO;GACR,CAAC;;AAIJ,QAAO,KAAK;EACV,MAAM;EACN,OAAO;GACL,eAAe,mBAAmB,WAAW,cAAc,YAAY;GACvE,OAAO,YAAY,UAAU;GAC9B;EACF,CAAC;AAEF,QAAO;;AAGT,SAAS,4CACP,SACA,WACA,WACA,QACA,WACA,WACkB;CAClB,MAAM,QAAQ,WAAW,MAAMC,mCAAmB;CAClD,MAAM,SAA2B,EAAE;AAGnC,QAAO,KAAK;EACV,IAAI;EACJ,MAAM;EACN,OAAO,EACL,SAAS;GACP,MAAM;GACN,SAAS,EAAE;GACX,WAAW;GACX,YAAY,EAAE;GACd,WAAW,EAAE;GACd,EACF;EACF,CAAC;CAEF,IAAI,eAAe;AAGnB,KAAI,WAAW;AACb,SAAO,KAAK;GACV,MAAM;GACN,OAAO;GACP,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,QAAQ,EAAE,EAAE;GAClD,CAAC;AACF,OAAK,IAAI,IAAI,GAAG,IAAI,UAAU,QAAQ,KAAK,WAAW;GACpD,MAAM,QAAQ,UAAU,MAAM,GAAG,IAAI,UAAU;AAC/C,UAAO,KAAK;IACV,MAAM;IACN,OAAO;IACP,OAAO,EAAE,SAAS,EAAE,SAAS;KAAE,MAAM;KAAQ,MAAM;KAAO,EAAE,EAAE;IAC/D,CAAC;;AAEJ,SAAO,KAAK;GAAE,MAAM;GAAe,OAAO;GAAc,CAAC;AACzD;;AAIF,QAAO,KAAK;EACV,MAAM;EACN,OAAO;EACP,OAAO,EACL,SAAS,EACP,SAAS,EAAE,MAAM,QAAQ,EAC1B,EACF;EACF,CAAC;AAGF,MAAK,IAAI,IAAI,GAAG,IAAI,QAAQ,QAAQ,KAAK,WAAW;EAClD,MAAM,QAAQ,QAAQ,MAAM,GAAG,IAAI,UAAU;AAC7C,SAAO,KAAK;GACV,MAAM;GACN,OAAO;GACP,OAAO,EACL,SAAS,EACP,SAAS;IAAE,MAAM;IAAQ,MAAM;IAAO,EACvC,EACF;GACF,CAAC;;AAIJ,QAAO,KAAK;EACV,MAAM;EACN,OAAO;EACR,CAAC;AAGF,QAAO,KAAK;EACV,MAAM;EACN,OAAO,EACL,SAAS,EACP,WAAW,kCACZ,EACF;EACF,CAAC;AAGF,MAAK,IAAI,MAAM,GAAG,MAAM,UAAU,QAAQ,OAAO;EAC/C,MAAM,KAAK,UAAU;EACrB,MAAM,SAAS,GAAG,MAAMD,oCAAoB;EAE5C,IAAI;AACJ,MAAI;AACF,QAAK,MAAM,GAAG,aAAa,KAAK;AAChC,cAAW,GAAG,aAAa;UACrB;AACN,UAAO,KACL,sDAAsD,GAAG,KAAK,KAAK,GAAG,YACvE;AACD,cAAW;;AAIb,SAAO,KAAK;GACV,MAAM;GACN,OAAO;GACP,OAAO,EACL,SAAS,EACP,YAAY;IACV,IAAI;IACJ,MAAM;IACN,UAAU;KACR,MAAM,GAAG;KACT,WAAW;KACZ;IACF,EACF,EACF;GACF,CAAC;AAGF,OAAK,IAAI,IAAI,GAAG,IAAI,SAAS,QAAQ,KAAK,WAAW;GACnD,MAAM,QAAQ,SAAS,MAAM,GAAG,IAAI,UAAU;AAC9C,UAAO,KAAK;IACV,MAAM;IACN,OAAO;IACP,OAAO,EACL,SAAS,EACP,YAAY,EACV,UAAU,EACR,WAAW,OACZ,EACF,EACF,EACF;IACF,CAAC;;AAIJ,SAAO,KAAK;GACV,MAAM;GACN,OAAO;GACR,CAAC;;AAIJ,QAAO,KAAK;EACV,MAAM;EACN,OAAO;GACL,eAAe,mBAAmB,WAAW,cAAc,YAAY;GACvE,OAAO,YAAY,UAAU;GAC9B;EACF,CAAC;AAEF,QAAO;;AAYT,eAAe,qBACb,KACA,QACA,kBACkB;CAClB,MAAM,OACJ,OAAO,qBAAqB,WAAW,EAAE,SAAS,kBAAkB,GAAI,oBAAoB,EAAE;CAChG,MAAM,UAAU,KAAK,WAAW;CAChC,MAAM,UAAU,KAAK;CACrB,MAAM,SAAS,KAAK;CACpB,MAAM,cAAc,KAAK;AAEzB,KAAI,IAAI,cAAe,QAAO;AAC9B,KAAI,UAAU,gBAAgB,oBAAoB;AAClD,KAAI,UAAU,iBAAiB,WAAW;AAC1C,KAAI,UAAU,cAAc,aAAa;CAEzC,IAAI,aAAa;AACjB,MAAK,MAAM,SAAS,QAAQ;EAC1B,MAAM,aAAaE,kCAAe,YAAY,SAAS,QAAQ;AAC/D,MAAI,aAAa,EAAG,OAAMC,yBAAM,YAAY,OAAO;AACnD,MAAI,QAAQ,QAAS,QAAO;AAC5B,MAAI,IAAI,cAAe,QAAO;AAC9B,MAAI,MAAM,UAAU,MAAM,KAAK,UAAU,KAAK,UAAU,MAAM,CAAC,MAAM;AACrE,iBAAe;AACf,MAAI,QAAQ,QAAS,QAAO;AAC5B;;AAGF,KAAI,CAAC,IAAI,cACP,KAAI,KAAK;AAEX,QAAO;;AAKT,eAAsB,aACpB,KACA,KACA,KACA,UACA,SACA,UACA,gBACe;CACf,MAAM,EAAE,WAAW;AACnB,gBAAe,IAAI;CAEnB,IAAI;AACJ,KAAI;AACF,cAAY,KAAK,MAAM,IAAI;SACrB;AACN,UAAQ,IAAI;GACV,QAAQ,IAAI,UAAU;GACtB,MAAM,IAAI,OAAO;GACjB,SAASC,+BAAe,IAAI,QAAQ;GACpC,MAAM;GACN,UAAU;IAAE,QAAQ;IAAK,SAAS;IAAM;GACzC,CAAC;AACF,wCACE,KACA,KACA,KAAK,UAAU,EACb,OAAO;GACL,SAAS;GACT,MAAM;GACP,EACF,CAAC,CACH;AACD;;AAIF,KAAI,CAAC,UAAU,OAAO;AACpB,UAAQ,IAAI;GACV,QAAQ,IAAI,UAAU;GACtB,MAAM,IAAI,OAAO;GACjB,SAASA,+BAAe,IAAI,QAAQ;GACpC,MAAM;GACN,UAAU;IAAE,QAAQ;IAAK,SAAS;IAAM;GACzC,CAAC;AACF,wCACE,KACA,KACA,KAAK,UAAU,EACb,OAAO;GACL,SAAS;GACT,MAAM;GACP,EACF,CAAC,CACH;AACD;;AAGF,KAAI,CAAC,UAAU,YAAY,CAAC,MAAM,QAAQ,UAAU,SAAS,EAAE;AAC7D,UAAQ,IAAI;GACV,QAAQ,IAAI,UAAU;GACtB,MAAM,IAAI,OAAO;GACjB,SAASA,+BAAe,IAAI,QAAQ;GACpC,MAAM;GACN,UAAU;IAAE,QAAQ;IAAK,SAAS;IAAM;GACzC,CAAC;AACF,wCACE,KACA,KACA,KAAK,UAAU,EACb,OAAO;GACL,SAAS;GACT,MAAM;GACP,EACF,CAAC,CACH;AACD;;CAIF,MAAM,gBAAgB,0BAA0B,UAAU;AAC1D,eAAc,gBAAgB;CAE9B,MAAM,SAASC,0BAAU,IAAI;CAC7B,MAAM,UAAUC,4BACd,UACA,eACA,QAAQ,6BAA6B,OAAO,EAC5C,SAAS,iBACV;AAED,KAAI,SAAS;AACX,UAAQ,2BAA2B,SAAS,UAAU,OAAO;AAC7D,SAAO,MAAM,oBAAoB,KAAK,UAAU,QAAQ,MAAM,CAAC,MAAM,GAAG,IAAI,GAAG;OAE/E,QAAO,MAAM,iCAAiC;AAGhD,KACEC,yBACE,KACA,SACA,SAAS,OACT,IAAI,SACJ,SACA;EACE,QAAQ,IAAI,UAAU;EACtB,MAAM,IAAI,OAAO;EACjB,SAASH,+BAAe,IAAI,QAAQ;EACpC,MAAM;EACP,EACD,SAAS,UACT,SAAS,OACV,CAED;AAEF,KAAI,CAAC,SAAS;AACZ,MAAI,SAAS,QAWX;OAVgB,MAAMI,gCACpB,KACA,KACA,eACA,UACA,IAAI,OAAO,YACX,UACA,UACA,IACD,EACY;AACX,YAAQ,IAAI;KACV,QAAQ,IAAI,UAAU;KACtB,MAAM,IAAI,OAAO;KACjB,SAASJ,+BAAe,IAAI,QAAQ;KACpC,MAAM;KACN,UAAU;MAAE,QAAQ,IAAI,cAAc;MAAK,SAAS;MAAM,QAAQ;MAAS;KAC5E,CAAC;AACF;;;EAGJ,MAAM,eAAe,SAAS,SAAS,MAAM;EAC7C,MAAM,gBAAgB,SAAS,SAC3B,oCACA;AACJ,MAAI,SAAS,OACX,QAAO,MACL,kCAAkC,IAAI,UAAU,OAAO,GAAG,IAAI,OAAO,aACtE;AAEH,UAAQ,IAAI;GACV,QAAQ,IAAI,UAAU;GACtB,MAAM,IAAI,OAAO;GACjB,SAASA,+BAAe,IAAI,QAAQ;GACpC,MAAM;GACN,UAAU;IAAE,QAAQ;IAAc,SAAS;IAAM;GAClD,CAAC;AACF,wCACE,KACA,cACA,KAAK,UAAU,EACb,OAAO;GACL,SAAS;GACT,MAAM;GACP,EACF,CAAC,CACH;AACD;;CAGF,MAAM,WAAW,QAAQ;CACzB,MAAM,UAAU,QAAQ,WAAW,SAAS;CAC5C,MAAM,YAAY,KAAK,IAAI,GAAG,QAAQ,aAAa,SAAS,UAAU;AAGtE,KAAIK,gCAAgB,SAAS,EAAE;EAC7B,MAAM,SAAS,SAAS,UAAU;AAClC,UAAQ,IAAI;GACV,QAAQ,IAAI,UAAU;GACtB,MAAM,IAAI,OAAO;GACjB,SAASL,+BAAe,IAAI,QAAQ;GACpC,MAAM;GACN,UAAU;IAAE;IAAQ;IAAS;GAC9B,CAAC;AACF,wCAAmB,KAAK,QAAQ,KAAK,UAAU,SAAS,CAAC;AACzD;;AAIF,KAAIM,+CAA+B,SAAS,EAAE;AAC5C,MAAI,SAAS,aAAa,OACxB,QAAO,KACL,sFACD;EAEH,MAAM,YAAYC,iCAAiB,SAAS;EAC5C,MAAM,eAAe,QAAQ,IAAI;GAC/B,QAAQ,IAAI,UAAU;GACtB,MAAM,IAAI,OAAO;GACjB,SAASP,+BAAe,IAAI,QAAQ;GACpC,MAAM;GACN,UAAU;IAAE,QAAQ;IAAK;IAAS;GACnC,CAAC;AACF,MAAI,UAAU,WAAW,MAAM;GAC7B,MAAM,OAAO,wCACX,SAAS,SACT,SAAS,WACT,QACA,SAAS,WACT,UACD;AACD,OAAI,UAAU,KAAK,EAAE,gBAAgB,oBAAoB,CAAC;AAC1D,OAAI,IAAI,KAAK,UAAU,KAAK,CAAC;SACxB;GACL,MAAM,SAAS,4CACb,SAAS,SACT,SAAS,WACT,WACA,QACA,SAAS,WACT,UACD;GACD,MAAM,eAAeQ,8CAAyB,QAAQ;AAOtD,OAAI,CANc,MAAM,qBAAqB,KAAK,QAAQ;IACxD;IACA,kBAAkB,QAAQ;IAC1B,QAAQ,cAAc;IACtB,aAAa,cAAc;IAC5B,CAAC,EACc;AACd,QAAI,CAAC,IAAI,cAAe,KAAI,SAAS;AACrC,iBAAa,SAAS,cAAc;AACpC,iBAAa,SAAS,kBAAkB,cAAc,QAAQ;;AAEhE,iBAAc,SAAS;;AAEzB;;AAIF,KAAIC,+BAAe,SAAS,EAAE;AAC5B,MAAI,SAAS,aAAa,OACxB,QAAO,KACL,sFACD;EAEH,MAAM,YAAYF,iCAAiB,SAAS;EAC5C,MAAM,eAAe,QAAQ,IAAI;GAC/B,QAAQ,IAAI,UAAU;GACtB,MAAM,IAAI,OAAO;GACjB,SAASP,+BAAe,IAAI,QAAQ;GACpC,MAAM;GACN,UAAU;IAAE,QAAQ;IAAK;IAAS;GACnC,CAAC;AACF,MAAI,UAAU,WAAW,MAAM;GAC7B,MAAM,OAAO,wBAAwB,SAAS,SAAS,SAAS,WAAW,UAAU;AACrF,OAAI,UAAU,KAAK,EAAE,gBAAgB,oBAAoB,CAAC;AAC1D,OAAI,IAAI,KAAK,UAAU,KAAK,CAAC;SACxB;GACL,MAAM,SAAS,4BACb,SAAS,SACT,WACA,SAAS,WACT,UACD;GACD,MAAM,eAAeQ,8CAAyB,QAAQ;AAOtD,OAAI,CANc,MAAM,qBAAqB,KAAK,QAAQ;IACxD;IACA,kBAAkB,QAAQ;IAC1B,QAAQ,cAAc;IACtB,aAAa,cAAc;IAC5B,CAAC,EACc;AACd,QAAI,CAAC,IAAI,cAAe,KAAI,SAAS;AACrC,iBAAa,SAAS,cAAc;AACpC,iBAAa,SAAS,kBAAkB,cAAc,QAAQ;;AAEhE,iBAAc,SAAS;;AAEzB;;AAIF,KAAIE,mCAAmB,SAAS,EAAE;EAChC,MAAM,YAAYH,iCAAiB,SAAS;EAC5C,MAAM,eAAe,QAAQ,IAAI;GAC/B,QAAQ,IAAI,UAAU;GACtB,MAAM,IAAI,OAAO;GACjB,SAASP,+BAAe,IAAI,QAAQ;GACpC,MAAM;GACN,UAAU;IAAE,QAAQ;IAAK;IAAS;GACnC,CAAC;AACF,MAAI,UAAU,WAAW,MAAM;GAC7B,MAAM,OAAO,4BAA4B,SAAS,WAAW,QAAQ,UAAU;AAC/E,OAAI,UAAU,KAAK,EAAE,gBAAgB,oBAAoB,CAAC;AAC1D,OAAI,IAAI,KAAK,UAAU,KAAK,CAAC;SACxB;GACL,MAAM,SAAS,gCACb,SAAS,WACT,WACA,QACA,UACD;GACD,MAAM,eAAeQ,8CAAyB,QAAQ;AAOtD,OAAI,CANc,MAAM,qBAAqB,KAAK,QAAQ;IACxD;IACA,kBAAkB,QAAQ;IAC1B,QAAQ,cAAc;IACtB,aAAa,cAAc;IAC5B,CAAC,EACc;AACd,QAAI,CAAC,IAAI,cAAe,KAAI,SAAS;AACrC,iBAAa,SAAS,cAAc;AACpC,iBAAa,SAAS,kBAAkB,cAAc,QAAQ;;AAEhE,iBAAc,SAAS;;AAEzB;;AAIF,SAAQ,IAAI;EACV,QAAQ,IAAI,UAAU;EACtB,MAAM,IAAI,OAAO;EACjB,SAASR,+BAAe,IAAI,QAAQ;EACpC,MAAM;EACN,UAAU;GAAE,QAAQ;GAAK;GAAS;EACnC,CAAC;AACF,uCACE,KACA,KACA,KAAK,UAAU,EACb,OAAO;EACL,SAAS;EACT,MAAM;EACP,EACF,CAAC,CACH"}
|
|
1
|
+
{"version":3,"file":"cohere.cjs","names":["generateToolCallId","generateMessageId","calculateDelay","delay","flattenHeaders","getTestId","matchFixture","applyChaos","proxyAndRecord","isErrorResponse","isContentWithToolCallsResponse","extractOverrides","createInterruptionSignal","isTextResponse","isToolCallResponse"],"sources":["../src/cohere.ts"],"sourcesContent":["/**\n * Cohere v2 Chat API endpoint support.\n *\n * Translates incoming /v2/chat requests into the ChatCompletionRequest\n * format used by the fixture router, and converts fixture responses back into\n * Cohere's typed SSE streaming (or non-streaming) format.\n *\n * Cohere uses typed SSE events (event: + data: lines), similar to the\n * Claude Messages handler in messages.ts.\n */\n\nimport type * as http from \"node:http\";\nimport type {\n ChatCompletionRequest,\n ChatMessage,\n Fixture,\n HandlerDefaults,\n ResponseOverrides,\n StreamingProfile,\n ToolCall,\n ToolDefinition,\n} from \"./types.js\";\nimport {\n generateMessageId,\n generateToolCallId,\n extractOverrides,\n isTextResponse,\n isToolCallResponse,\n isContentWithToolCallsResponse,\n isErrorResponse,\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// ─── Cohere v2 Chat request types ───────────────────────────────────────────\n\ninterface CohereToolCallDef {\n id?: string;\n type: string;\n function: {\n name: string;\n arguments: string;\n };\n}\n\ninterface CohereMessage {\n role: \"user\" | \"assistant\" | \"system\" | \"tool\";\n content: string;\n tool_call_id?: string;\n tool_calls?: CohereToolCallDef[];\n}\n\ninterface CohereToolDef {\n type: string;\n function: {\n name: string;\n description?: string;\n parameters?: object;\n };\n}\n\ninterface CohereRequest {\n model: string;\n messages: CohereMessage[];\n stream?: boolean;\n tools?: CohereToolDef[];\n response_format?: { type: string; json_schema?: object };\n}\n\n// ─── Cohere SSE event types ─────────────────────────────────────────────────\n\ninterface CohereSSEEvent {\n type: string;\n [key: string]: unknown;\n}\n\n// ─── Zero-value usage block ─────────────────────────────────────────────────\n\nconst ZERO_USAGE = {\n billed_units: { input_tokens: 0, output_tokens: 0, search_units: 0, classifications: 0 },\n tokens: { input_tokens: 0, output_tokens: 0 },\n};\n\n// ─── Cohere finish reason / usage mapping ──────────────────────────────────\n\nfunction cohereFinishReason(\n overrideFinishReason: string | undefined,\n defaultReason: string,\n): string {\n if (!overrideFinishReason) return defaultReason;\n if (overrideFinishReason === \"stop\") return \"COMPLETE\";\n if (overrideFinishReason === \"tool_calls\") return \"TOOL_CALL\";\n if (overrideFinishReason === \"length\") return \"MAX_TOKENS\";\n return overrideFinishReason;\n}\n\nfunction cohereUsage(overrides?: ResponseOverrides): typeof ZERO_USAGE {\n if (!overrides?.usage) return ZERO_USAGE;\n const inputTokens = overrides.usage.input_tokens ?? overrides.usage.prompt_tokens ?? 0;\n const outputTokens = overrides.usage.output_tokens ?? overrides.usage.completion_tokens ?? 0;\n return {\n billed_units: {\n input_tokens: inputTokens,\n output_tokens: outputTokens,\n search_units: 0,\n classifications: 0,\n },\n tokens: { input_tokens: inputTokens, output_tokens: outputTokens },\n };\n}\n\n// ─── Input conversion: Cohere → ChatCompletionRequest ───────────────────────\n\nexport function cohereToCompletionRequest(req: CohereRequest): ChatCompletionRequest {\n const messages: ChatMessage[] = [];\n\n for (const msg of req.messages) {\n if (msg.role === \"system\") {\n messages.push({ role: \"system\", content: msg.content });\n } else if (msg.role === \"user\") {\n messages.push({ role: \"user\", content: msg.content });\n } else if (msg.role === \"assistant\") {\n if (msg.tool_calls && msg.tool_calls.length > 0) {\n messages.push({\n role: \"assistant\",\n content: msg.content || null,\n tool_calls: msg.tool_calls.map((tc) => ({\n id: tc.id ?? generateToolCallId(),\n type: \"function\" as const,\n function: {\n name: tc.function.name,\n arguments: tc.function.arguments,\n },\n })),\n });\n } else {\n messages.push({ role: \"assistant\", content: msg.content });\n }\n } else if (msg.role === \"tool\") {\n messages.push({\n role: \"tool\",\n content: msg.content,\n tool_call_id: msg.tool_call_id,\n });\n }\n }\n\n // Convert tools\n let tools: ToolDefinition[] | undefined;\n if (req.tools && req.tools.length > 0) {\n tools = req.tools.map((t) => ({\n type: \"function\" as const,\n function: {\n name: t.function.name,\n description: t.function.description,\n parameters: t.function.parameters,\n },\n }));\n }\n\n return {\n model: req.model,\n messages,\n stream: req.stream,\n tools,\n ...(req.response_format && { response_format: req.response_format }),\n };\n}\n\n// ─── Response building: fixture → Cohere v2 Chat format ─────────────────────\n\n// Non-streaming text response\nfunction buildCohereTextResponse(\n content: string,\n reasoning?: string,\n overrides?: ResponseOverrides,\n): object {\n const contentBlocks: { type: string; text: string }[] = [];\n if (reasoning) {\n contentBlocks.push({ type: \"text\", text: reasoning });\n }\n contentBlocks.push({ type: \"text\", text: content });\n\n return {\n id: overrides?.id ?? generateMessageId(),\n finish_reason: cohereFinishReason(overrides?.finishReason, \"COMPLETE\"),\n message: {\n role: \"assistant\",\n content: contentBlocks,\n tool_calls: [],\n tool_plan: \"\",\n citations: [],\n },\n usage: cohereUsage(overrides),\n };\n}\n\n// Non-streaming tool call response\nfunction buildCohereToolCallResponse(\n toolCalls: ToolCall[],\n logger: Logger,\n overrides?: ResponseOverrides,\n): object {\n const cohereCalls = toolCalls.map((tc) => {\n // Validate arguments JSON\n let argsJson: string;\n try {\n JSON.parse(tc.arguments || \"{}\");\n argsJson = tc.arguments || \"{}\";\n } catch {\n logger.warn(\n `Malformed JSON in fixture tool call arguments for \"${tc.name}\": ${tc.arguments}`,\n );\n argsJson = \"{}\";\n }\n return {\n id: tc.id || generateToolCallId(),\n type: \"function\",\n function: {\n name: tc.name,\n arguments: argsJson,\n },\n };\n });\n\n return {\n id: overrides?.id ?? generateMessageId(),\n finish_reason: cohereFinishReason(overrides?.finishReason, \"TOOL_CALL\"),\n message: {\n role: \"assistant\",\n content: [],\n tool_calls: cohereCalls,\n tool_plan: \"\",\n citations: [],\n },\n usage: cohereUsage(overrides),\n };\n}\n\n// Non-streaming content + tool calls response\nfunction buildCohereContentWithToolCallsResponse(\n content: string,\n toolCalls: ToolCall[],\n logger: Logger,\n reasoning?: string,\n overrides?: ResponseOverrides,\n): object {\n const cohereCalls = toolCalls.map((tc) => {\n let argsJson: string;\n try {\n JSON.parse(tc.arguments || \"{}\");\n argsJson = tc.arguments || \"{}\";\n } catch {\n logger.warn(\n `Malformed JSON in fixture tool call arguments for \"${tc.name}\": ${tc.arguments}`,\n );\n argsJson = \"{}\";\n }\n return {\n id: tc.id || generateToolCallId(),\n type: \"function\",\n function: {\n name: tc.name,\n arguments: argsJson,\n },\n };\n });\n\n const contentBlocks: { type: string; text: string }[] = [];\n if (reasoning) {\n contentBlocks.push({ type: \"text\", text: reasoning });\n }\n contentBlocks.push({ type: \"text\", text: content });\n\n return {\n id: overrides?.id ?? generateMessageId(),\n finish_reason: cohereFinishReason(overrides?.finishReason, \"TOOL_CALL\"),\n message: {\n role: \"assistant\",\n content: contentBlocks,\n tool_calls: cohereCalls,\n tool_plan: \"\",\n citations: [],\n },\n usage: cohereUsage(overrides),\n };\n}\n\n// ─── Streaming event builders ───────────────────────────────────────────────\n\nfunction buildCohereTextStreamEvents(\n content: string,\n chunkSize: number,\n reasoning?: string,\n overrides?: ResponseOverrides,\n): CohereSSEEvent[] {\n const msgId = overrides?.id ?? generateMessageId();\n const events: CohereSSEEvent[] = [];\n\n // message-start\n events.push({\n id: msgId,\n type: \"message-start\",\n delta: {\n message: {\n role: \"assistant\",\n content: [],\n tool_plan: \"\",\n tool_calls: [],\n citations: [],\n },\n },\n });\n\n let contentIndex = 0;\n\n // Reasoning as a text block before main content (Cohere has no native reasoning type)\n if (reasoning) {\n events.push({\n type: \"content-start\",\n index: contentIndex,\n delta: { message: { content: { type: \"text\" } } },\n });\n for (let i = 0; i < reasoning.length; i += chunkSize) {\n const slice = reasoning.slice(i, i + chunkSize);\n events.push({\n type: \"content-delta\",\n index: contentIndex,\n delta: { message: { content: { type: \"text\", text: slice } } },\n });\n }\n events.push({ type: \"content-end\", index: contentIndex });\n contentIndex++;\n }\n\n // content-start (type: \"text\" only, no text field)\n events.push({\n type: \"content-start\",\n index: contentIndex,\n delta: {\n message: {\n content: { type: \"text\" },\n },\n },\n });\n\n // content-delta — text chunks\n for (let i = 0; i < content.length; i += chunkSize) {\n const slice = content.slice(i, i + chunkSize);\n events.push({\n type: \"content-delta\",\n index: contentIndex,\n delta: {\n message: {\n content: { type: \"text\", text: slice },\n },\n },\n });\n }\n\n // content-end\n events.push({\n type: \"content-end\",\n index: contentIndex,\n });\n\n // message-end\n events.push({\n type: \"message-end\",\n delta: {\n finish_reason: cohereFinishReason(overrides?.finishReason, \"COMPLETE\"),\n usage: cohereUsage(overrides),\n },\n });\n\n return events;\n}\n\nfunction buildCohereToolCallStreamEvents(\n toolCalls: ToolCall[],\n chunkSize: number,\n logger: Logger,\n overrides?: ResponseOverrides,\n): CohereSSEEvent[] {\n const msgId = overrides?.id ?? generateMessageId();\n const events: CohereSSEEvent[] = [];\n\n // message-start\n events.push({\n id: msgId,\n type: \"message-start\",\n delta: {\n message: {\n role: \"assistant\",\n content: [],\n tool_plan: \"\",\n tool_calls: [],\n citations: [],\n },\n },\n });\n\n // tool-plan-delta\n events.push({\n type: \"tool-plan-delta\",\n delta: {\n message: {\n tool_plan: \"I will use the requested tool.\",\n },\n },\n });\n\n for (let idx = 0; idx < toolCalls.length; idx++) {\n const tc = toolCalls[idx];\n const callId = tc.id || generateToolCallId();\n\n // Validate arguments JSON\n let argsJson: string;\n try {\n JSON.parse(tc.arguments || \"{}\");\n argsJson = tc.arguments || \"{}\";\n } catch {\n logger.warn(\n `Malformed JSON in fixture tool call arguments for \"${tc.name}\": ${tc.arguments}`,\n );\n argsJson = \"{}\";\n }\n\n // tool-call-start\n events.push({\n type: \"tool-call-start\",\n index: idx,\n delta: {\n message: {\n tool_calls: {\n id: callId,\n type: \"function\",\n function: {\n name: tc.name,\n arguments: \"\",\n },\n },\n },\n },\n });\n\n // tool-call-delta — chunked arguments\n for (let i = 0; i < argsJson.length; i += chunkSize) {\n const slice = argsJson.slice(i, i + chunkSize);\n events.push({\n type: \"tool-call-delta\",\n index: idx,\n delta: {\n message: {\n tool_calls: {\n function: {\n arguments: slice,\n },\n },\n },\n },\n });\n }\n\n // tool-call-end\n events.push({\n type: \"tool-call-end\",\n index: idx,\n });\n }\n\n // message-end\n events.push({\n type: \"message-end\",\n delta: {\n finish_reason: cohereFinishReason(overrides?.finishReason, \"TOOL_CALL\"),\n usage: cohereUsage(overrides),\n },\n });\n\n return events;\n}\n\nfunction buildCohereContentWithToolCallsStreamEvents(\n content: string,\n toolCalls: ToolCall[],\n chunkSize: number,\n logger: Logger,\n reasoning?: string,\n overrides?: ResponseOverrides,\n): CohereSSEEvent[] {\n const msgId = overrides?.id ?? generateMessageId();\n const events: CohereSSEEvent[] = [];\n\n // message-start\n events.push({\n id: msgId,\n type: \"message-start\",\n delta: {\n message: {\n role: \"assistant\",\n content: [],\n tool_plan: \"\",\n tool_calls: [],\n citations: [],\n },\n },\n });\n\n let contentIndex = 0;\n\n // Reasoning as a text block before main content\n if (reasoning) {\n events.push({\n type: \"content-start\",\n index: contentIndex,\n delta: { message: { content: { type: \"text\" } } },\n });\n for (let i = 0; i < reasoning.length; i += chunkSize) {\n const slice = reasoning.slice(i, i + chunkSize);\n events.push({\n type: \"content-delta\",\n index: contentIndex,\n delta: { message: { content: { type: \"text\", text: slice } } },\n });\n }\n events.push({ type: \"content-end\", index: contentIndex });\n contentIndex++;\n }\n\n // content-start (type: \"text\" only, no text field)\n events.push({\n type: \"content-start\",\n index: contentIndex,\n delta: {\n message: {\n content: { type: \"text\" },\n },\n },\n });\n\n // content-delta — text chunks\n for (let i = 0; i < content.length; i += chunkSize) {\n const slice = content.slice(i, i + chunkSize);\n events.push({\n type: \"content-delta\",\n index: contentIndex,\n delta: {\n message: {\n content: { type: \"text\", text: slice },\n },\n },\n });\n }\n\n // content-end\n events.push({\n type: \"content-end\",\n index: contentIndex,\n });\n\n // tool-plan-delta\n events.push({\n type: \"tool-plan-delta\",\n delta: {\n message: {\n tool_plan: \"I will use the requested tool.\",\n },\n },\n });\n\n // Tool call events\n for (let idx = 0; idx < toolCalls.length; idx++) {\n const tc = toolCalls[idx];\n const callId = tc.id || generateToolCallId();\n\n let argsJson: string;\n try {\n JSON.parse(tc.arguments || \"{}\");\n argsJson = tc.arguments || \"{}\";\n } catch {\n logger.warn(\n `Malformed JSON in fixture tool call arguments for \"${tc.name}\": ${tc.arguments}`,\n );\n argsJson = \"{}\";\n }\n\n // tool-call-start\n events.push({\n type: \"tool-call-start\",\n index: idx,\n delta: {\n message: {\n tool_calls: {\n id: callId,\n type: \"function\",\n function: {\n name: tc.name,\n arguments: \"\",\n },\n },\n },\n },\n });\n\n // tool-call-delta — chunked arguments\n for (let i = 0; i < argsJson.length; i += chunkSize) {\n const slice = argsJson.slice(i, i + chunkSize);\n events.push({\n type: \"tool-call-delta\",\n index: idx,\n delta: {\n message: {\n tool_calls: {\n function: {\n arguments: slice,\n },\n },\n },\n },\n });\n }\n\n // tool-call-end\n events.push({\n type: \"tool-call-end\",\n index: idx,\n });\n }\n\n // message-end\n events.push({\n type: \"message-end\",\n delta: {\n finish_reason: cohereFinishReason(overrides?.finishReason, \"TOOL_CALL\"),\n usage: cohereUsage(overrides),\n },\n });\n\n return events;\n}\n\n// ─── SSE writer for Cohere typed events ─────────────────────────────────────\n\ninterface CohereStreamOptions {\n latency?: number;\n streamingProfile?: StreamingProfile;\n signal?: AbortSignal;\n onChunkSent?: () => void;\n}\n\nasync function writeCohereSSEStream(\n res: http.ServerResponse,\n events: CohereSSEEvent[],\n optionsOrLatency?: number | CohereStreamOptions,\n): Promise<boolean> {\n const opts: CohereStreamOptions =\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 event of events) {\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 res.write(`event: ${event.type}\\ndata: ${JSON.stringify(event)}\\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 handleCohere(\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 cohereReq: CohereRequest;\n try {\n cohereReq = JSON.parse(raw) as CohereRequest;\n } catch {\n journal.add({\n method: req.method ?? \"POST\",\n path: req.url ?? \"/v2/chat\",\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 },\n }),\n );\n return;\n }\n\n // Validate required model field\n if (!cohereReq.model) {\n journal.add({\n method: req.method ?? \"POST\",\n path: req.url ?? \"/v2/chat\",\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: \"model is required\",\n type: \"invalid_request_error\",\n },\n }),\n );\n return;\n }\n\n if (!cohereReq.messages || !Array.isArray(cohereReq.messages)) {\n journal.add({\n method: req.method ?? \"POST\",\n path: req.url ?? \"/v2/chat\",\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: \"Invalid request: messages array is required\",\n type: \"invalid_request_error\",\n },\n }),\n );\n return;\n }\n\n // Convert to ChatCompletionRequest for fixture matching\n const completionReq = cohereToCompletionRequest(cohereReq);\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\n if (fixture) {\n journal.incrementFixtureMatchCount(fixture, fixtures, testId);\n logger.debug(`Fixture matched: ${JSON.stringify(fixture.match).slice(0, 120)}`);\n } else {\n logger.debug(`No fixture matched for request`);\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 ?? \"/v2/chat\",\n headers: flattenHeaders(req.headers),\n body: completionReq,\n },\n fixture ? \"fixture\" : \"proxy\",\n defaults.registry,\n defaults.logger,\n )\n )\n return;\n\n if (!fixture) {\n if (defaults.record) {\n const outcome = await proxyAndRecord(\n req,\n res,\n completionReq,\n \"cohere\",\n req.url ?? \"/v2/chat\",\n fixtures,\n defaults,\n raw,\n );\n if (outcome !== \"not_configured\") {\n journal.add({\n method: req.method ?? \"POST\",\n path: req.url ?? \"/v2/chat\",\n headers: flattenHeaders(req.headers),\n body: completionReq,\n response: { status: res.statusCode ?? 200, fixture: null, source: \"proxy\" },\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(\n `STRICT: No fixture matched for ${req.method ?? \"POST\"} ${req.url ?? \"/v2/chat\"}`,\n );\n }\n journal.add({\n method: req.method ?? \"POST\",\n path: req.url ?? \"/v2/chat\",\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 type: \"invalid_request_error\",\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: req.url ?? \"/v2/chat\",\n headers: flattenHeaders(req.headers),\n body: completionReq,\n response: { status, fixture },\n });\n writeErrorResponse(res, status, JSON.stringify(response));\n return;\n }\n\n // Content + tool calls response (must be checked before text/tool-only branches)\n if (isContentWithToolCallsResponse(response)) {\n if (response.webSearches?.length) {\n logger.warn(\n \"webSearches in fixture response are not supported for Cohere v2 Chat API — ignoring\",\n );\n }\n const overrides = extractOverrides(response);\n const journalEntry = journal.add({\n method: req.method ?? \"POST\",\n path: req.url ?? \"/v2/chat\",\n headers: flattenHeaders(req.headers),\n body: completionReq,\n response: { status: 200, fixture },\n });\n if (cohereReq.stream !== true) {\n const body = buildCohereContentWithToolCallsResponse(\n response.content,\n response.toolCalls,\n logger,\n response.reasoning,\n overrides,\n );\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify(body));\n } else {\n const events = buildCohereContentWithToolCallsStreamEvents(\n response.content,\n response.toolCalls,\n chunkSize,\n logger,\n response.reasoning,\n overrides,\n );\n const interruption = createInterruptionSignal(fixture);\n const completed = await writeCohereSSEStream(res, events, {\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 if (response.webSearches?.length) {\n logger.warn(\n \"webSearches in fixture response are not supported for Cohere v2 Chat API — ignoring\",\n );\n }\n const overrides = extractOverrides(response);\n const journalEntry = journal.add({\n method: req.method ?? \"POST\",\n path: req.url ?? \"/v2/chat\",\n headers: flattenHeaders(req.headers),\n body: completionReq,\n response: { status: 200, fixture },\n });\n if (cohereReq.stream !== true) {\n const body = buildCohereTextResponse(response.content, response.reasoning, overrides);\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify(body));\n } else {\n const events = buildCohereTextStreamEvents(\n response.content,\n chunkSize,\n response.reasoning,\n overrides,\n );\n const interruption = createInterruptionSignal(fixture);\n const completed = await writeCohereSSEStream(res, events, {\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 overrides = extractOverrides(response);\n const journalEntry = journal.add({\n method: req.method ?? \"POST\",\n path: req.url ?? \"/v2/chat\",\n headers: flattenHeaders(req.headers),\n body: completionReq,\n response: { status: 200, fixture },\n });\n if (cohereReq.stream !== true) {\n const body = buildCohereToolCallResponse(response.toolCalls, logger, overrides);\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify(body));\n } else {\n const events = buildCohereToolCallStreamEvents(\n response.toolCalls,\n chunkSize,\n logger,\n overrides,\n );\n const interruption = createInterruptionSignal(fixture);\n const completed = await writeCohereSSEStream(res, events, {\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: req.url ?? \"/v2/chat\",\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 type: \"server_error\",\n },\n }),\n );\n}\n"],"mappings":"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"}
|
package/dist/cohere.js
CHANGED
|
@@ -517,10 +517,10 @@ async function handleCohere(req, res, raw, fixtures, journal, defaults, setCorsH
|
|
|
517
517
|
path: req.url ?? "/v2/chat",
|
|
518
518
|
headers: flattenHeaders(req.headers),
|
|
519
519
|
body: completionReq
|
|
520
|
-
}, defaults.registry, defaults.logger)) return;
|
|
520
|
+
}, fixture ? "fixture" : "proxy", defaults.registry, defaults.logger)) return;
|
|
521
521
|
if (!fixture) {
|
|
522
522
|
if (defaults.record) {
|
|
523
|
-
if (await proxyAndRecord(req, res, completionReq, "cohere", req.url ?? "/v2/chat", fixtures, defaults, raw)) {
|
|
523
|
+
if (await proxyAndRecord(req, res, completionReq, "cohere", req.url ?? "/v2/chat", fixtures, defaults, raw) !== "not_configured") {
|
|
524
524
|
journal.add({
|
|
525
525
|
method: req.method ?? "POST",
|
|
526
526
|
path: req.url ?? "/v2/chat",
|