llm-mock-server 1.0.0 → 1.0.2
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/skills/desloppify/SKILL.md +308 -0
- package/.desloppify/external_review_sessions/ext_20260315_000339_a6cdc3e6/canonical_import_20260315_000801.json +242 -0
- package/.desloppify/external_review_sessions/ext_20260315_000339_a6cdc3e6/canonical_import_20260315_000905.json +248 -0
- package/.desloppify/external_review_sessions/ext_20260315_000339_a6cdc3e6/canonical_import_20260315_000917.json +248 -0
- package/.desloppify/external_review_sessions/ext_20260315_000339_a6cdc3e6/canonical_import_20260315_000950.json +311 -0
- package/.desloppify/external_review_sessions/ext_20260315_000339_a6cdc3e6/claude_launch_prompt.md +17 -0
- package/.desloppify/external_review_sessions/ext_20260315_000339_a6cdc3e6/review_result.json +255 -0
- package/.desloppify/external_review_sessions/ext_20260315_000339_a6cdc3e6/review_result.template.json +22 -0
- package/.desloppify/external_review_sessions/ext_20260315_000339_a6cdc3e6/reviewer_instructions.md +20 -0
- package/.desloppify/external_review_sessions/ext_20260315_000339_a6cdc3e6/session.json +20 -0
- package/.desloppify/query.json +284 -0
- package/.desloppify/review_packet_blind.json +1303 -0
- package/.desloppify/review_packets/holistic_packet_20260315_000339.json +1471 -0
- package/.desloppify/state-typescript.json +5114 -0
- package/.desloppify/state-typescript.json.bak +5108 -0
- package/dist/cli-validators.d.ts +7 -0
- package/dist/cli-validators.d.ts.map +1 -0
- package/dist/cli-validators.js +51 -0
- package/dist/cli-validators.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +106 -0
- package/dist/cli.js.map +1 -0
- package/dist/formats/anthropic/index.d.ts +3 -0
- package/dist/formats/anthropic/index.d.ts.map +1 -0
- package/dist/formats/anthropic/index.js +13 -0
- package/dist/formats/anthropic/index.js.map +1 -0
- package/dist/formats/anthropic/parse.d.ts +4 -0
- package/dist/formats/anthropic/parse.d.ts.map +1 -0
- package/dist/formats/anthropic/parse.js +47 -0
- package/dist/formats/anthropic/parse.js.map +1 -0
- package/dist/formats/anthropic/schema.d.ts +75 -0
- package/dist/formats/anthropic/schema.d.ts.map +1 -0
- package/dist/formats/anthropic/schema.js +50 -0
- package/dist/formats/anthropic/schema.js.map +1 -0
- package/dist/formats/anthropic/serialize.d.ts +10 -0
- package/dist/formats/anthropic/serialize.d.ts.map +1 -0
- package/dist/formats/anthropic/serialize.js +73 -0
- package/dist/formats/anthropic/serialize.js.map +1 -0
- package/dist/formats/openai/index.d.ts +3 -0
- package/dist/formats/openai/index.d.ts.map +1 -0
- package/dist/formats/openai/index.js +13 -0
- package/dist/formats/openai/index.js.map +1 -0
- package/dist/formats/openai/parse.d.ts +4 -0
- package/dist/formats/openai/parse.d.ts.map +1 -0
- package/dist/formats/openai/parse.js +33 -0
- package/dist/formats/openai/parse.js.map +1 -0
- package/dist/formats/openai/schema.d.ts +93 -0
- package/dist/formats/openai/schema.d.ts.map +1 -0
- package/dist/formats/openai/schema.js +68 -0
- package/dist/formats/openai/schema.js.map +1 -0
- package/dist/formats/openai/serialize.d.ts +10 -0
- package/dist/formats/openai/serialize.d.ts.map +1 -0
- package/dist/formats/openai/serialize.js +70 -0
- package/dist/formats/openai/serialize.js.map +1 -0
- package/dist/formats/parse-helpers.d.ts +24 -0
- package/dist/formats/parse-helpers.d.ts.map +1 -0
- package/dist/formats/parse-helpers.js +52 -0
- package/dist/formats/parse-helpers.js.map +1 -0
- package/dist/formats/request-helpers.d.ts +13 -0
- package/dist/formats/request-helpers.d.ts.map +1 -0
- package/dist/formats/request-helpers.js +28 -0
- package/dist/formats/request-helpers.js.map +1 -0
- package/dist/formats/responses/index.d.ts +3 -0
- package/dist/formats/responses/index.d.ts.map +1 -0
- package/dist/formats/responses/index.js +13 -0
- package/dist/formats/responses/index.js.map +1 -0
- package/dist/formats/responses/parse.d.ts +4 -0
- package/dist/formats/responses/parse.d.ts.map +1 -0
- package/dist/formats/responses/parse.js +51 -0
- package/dist/formats/responses/parse.js.map +1 -0
- package/dist/formats/responses/schema.d.ts +103 -0
- package/dist/formats/responses/schema.d.ts.map +1 -0
- package/dist/formats/responses/schema.js +61 -0
- package/dist/formats/responses/schema.js.map +1 -0
- package/dist/formats/responses/serialize.d.ts +10 -0
- package/dist/formats/responses/serialize.d.ts.map +1 -0
- package/dist/formats/responses/serialize.js +108 -0
- package/dist/formats/responses/serialize.js.map +1 -0
- package/dist/formats/serialize-helpers.d.ts +14 -0
- package/dist/formats/serialize-helpers.d.ts.map +1 -0
- package/dist/formats/serialize-helpers.js +25 -0
- package/dist/formats/serialize-helpers.js.map +1 -0
- package/dist/formats/types.d.ts +20 -0
- package/dist/formats/types.d.ts.map +1 -0
- package/dist/formats/types.js +2 -0
- package/dist/formats/types.js.map +1 -0
- package/dist/history.d.ts +38 -0
- package/dist/history.d.ts.map +1 -0
- package/dist/history.js +48 -0
- package/dist/history.js.map +1 -0
- package/dist/index.d.ts +21 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +20 -0
- package/dist/index.js.map +1 -0
- package/dist/loader.d.ts +9 -0
- package/dist/loader.d.ts.map +1 -0
- package/dist/loader.js +169 -0
- package/dist/loader.js.map +1 -0
- package/dist/logger.d.ts +21 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +42 -0
- package/dist/logger.js.map +1 -0
- package/dist/mock-server.d.ts +102 -0
- package/dist/mock-server.d.ts.map +1 -0
- package/dist/mock-server.js +195 -0
- package/dist/mock-server.js.map +1 -0
- package/dist/route-handler.d.ts +16 -0
- package/dist/route-handler.d.ts.map +1 -0
- package/dist/route-handler.js +75 -0
- package/dist/route-handler.js.map +1 -0
- package/dist/rule-engine.d.ts +24 -0
- package/dist/rule-engine.d.ts.map +1 -0
- package/dist/rule-engine.js +129 -0
- package/dist/rule-engine.js.map +1 -0
- package/dist/sse-writer.d.ts +5 -0
- package/dist/sse-writer.d.ts.map +1 -0
- package/dist/sse-writer.js +23 -0
- package/dist/sse-writer.js.map +1 -0
- package/{src/types/index.ts → dist/types/index.d.ts} +1 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +2 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/reply.d.ts +45 -0
- package/dist/types/reply.d.ts.map +1 -0
- package/dist/types/reply.js +2 -0
- package/dist/types/reply.js.map +1 -0
- package/dist/types/request.d.ts +39 -0
- package/dist/types/request.d.ts.map +1 -0
- package/dist/types/request.js +2 -0
- package/dist/types/request.js.map +1 -0
- package/dist/types/rule.d.ts +57 -0
- package/dist/types/rule.d.ts.map +1 -0
- package/dist/types/rule.js +2 -0
- package/dist/types/rule.js.map +1 -0
- package/dist/types.d.ts +4 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +2 -1
- package/scorecard.png +0 -0
- package/src/cli-validators.ts +3 -0
- package/src/cli.ts +6 -2
- package/src/formats/anthropic/index.ts +1 -1
- package/src/formats/anthropic/parse.ts +4 -4
- package/src/formats/anthropic/schema.ts +1 -68
- package/src/formats/anthropic/serialize.ts +9 -5
- package/src/formats/openai/index.ts +1 -1
- package/src/formats/openai/parse.ts +1 -1
- package/src/formats/openai/schema.ts +1 -69
- package/src/formats/openai/serialize.ts +15 -17
- package/src/formats/{parse-helpers.ts → request-helpers.ts} +2 -31
- package/src/formats/responses/index.ts +1 -1
- package/src/formats/responses/parse.ts +1 -1
- package/src/formats/responses/schema.ts +1 -72
- package/src/formats/responses/serialize.ts +9 -5
- package/src/formats/serialize-helpers.ts +30 -0
- package/src/formats/types.ts +3 -3
- package/src/loader.ts +7 -11
- package/src/logger.ts +19 -25
- package/src/mock-server.ts +10 -14
- package/src/route-handler.ts +1 -1
- package/src/rule-engine.ts +23 -1
- package/src/types/reply.ts +6 -10
- package/src/types/request.ts +7 -11
- package/src/types/rule.ts +3 -10
- package/src/types.ts +3 -5
- package/test/formats/anthropic.test.ts +4 -4
- package/test/formats/parse-helpers.test.ts +275 -0
- package/test/formats/responses.test.ts +4 -4
- package/test/helpers/make-req.ts +18 -0
- package/test/history.test.ts +348 -0
- package/test/loader.test.ts +11 -27
- package/test/logger.test.ts +294 -0
- package/test/mock-server.test.ts +1 -1
- package/test/rule-engine.test.ts +8 -22
- package/test/formats/anthropic-schema.test.ts +0 -192
- package/test/formats/openai-schema.test.ts +0 -105
- package/test/formats/responses-schema.test.ts +0 -114
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rule-engine.js","sourceRoot":"","sources":["../src/rule-engine.ts"],"names":[],"mappings":"AAEA,SAAS,SAAS,CAAC,EAAU;IAC3B,OAAO,CAAC,EAAE,CAAC,MAAM,IAAI,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;AAC9F,CAAC;AAED,SAAS,cAAc,CAAC,OAAwB;IAC9C,IAAI,OAAO,OAAO,KAAK,QAAQ,EAAE,CAAC;QAChC,MAAM,KAAK,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC;QACpC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;IACxD,CAAC;IACD,MAAM,EAAE,GAAG,SAAS,CAAC,OAAO,CAAC,CAAC;IAC9B,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;AACnC,CAAC;AAED,SAAS,cAAc,CAAC,KAAY;IAClC,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAC9B,MAAM,IAAI,GAAG,cAAc,CAAC,KAAK,CAAC,CAAC;QACnC,OAAO,CAAC,GAAG,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;IACxC,CAAC;IACD,IAAI,KAAK,YAAY,MAAM,EAAE,CAAC;QAC5B,MAAM,IAAI,GAAG,cAAc,CAAC,KAAK,CAAC,CAAC;QACnC,OAAO,CAAC,GAAG,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;IACxC,CAAC;IACD,IAAI,OAAO,KAAK,KAAK,UAAU,EAAE,CAAC;QAChC,OAAO,KAAK,CAAC;IACf,CAAC;IACD,MAAM,GAAG,GAAG,KAAK,CAAC;IAClB,MAAM,WAAW,GAAG,GAAG,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,cAAc,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IACxF,MAAM,SAAS,GAAG,GAAG,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,cAAc,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IAClF,MAAM,UAAU,GAAG,GAAG,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,cAAc,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IACrF,OAAO,CAAC,GAAG,EAAE,EAAE;QACb,IAAI,WAAW,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,WAAW,CAAC;YAAE,OAAO,KAAK,CAAC;QAC/D,IAAI,SAAS,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,KAAK,CAAC;YAAE,OAAO,KAAK,CAAC;QACrD,IAAI,UAAU,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,aAAa,CAAC;YAAE,OAAO,KAAK,CAAC;QAC/D,IAAI,GAAG,CAAC,MAAM,KAAK,SAAS,IAAI,GAAG,CAAC,MAAM,KAAK,GAAG,CAAC,MAAM;YAAE,OAAO,KAAK,CAAC;QACxE,IAAI,GAAG,CAAC,QAAQ,KAAK,SAAS,IAAI,CAAC,GAAG,CAAC,SAAS,CAAC,QAAQ,CAAC,GAAG,CAAC,QAAQ,CAAC;YAAE,OAAO,KAAK,CAAC;QACtF,IAAI,GAAG,CAAC,UAAU,KAAK,SAAS,IAAI,GAAG,CAAC,cAAc,KAAK,GAAG,CAAC,UAAU;YAAE,OAAO,KAAK,CAAC;QACxF,IAAI,GAAG,CAAC,SAAS,IAAI,CAAC,GAAG,CAAC,SAAS,CAAC,GAAG,CAAC;YAAE,OAAO,KAAK,CAAC;QACvD,OAAO,IAAI,CAAC;IACd,CAAC,CAAC;AACJ,CAAC;AAED,SAAS,aAAa,CAAC,KAAY;IACjC,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,IAAI,KAAK,GAAG,CAAC;IACnD,IAAI,KAAK,YAAY,MAAM;QAAE,OAAO,KAAK,CAAC,QAAQ,EAAE,CAAC;IACrD,IAAI,OAAO,KAAK,KAAK,UAAU;QAAE,OAAO,aAAa,CAAC;IACtD,MAAM,GAAG,GAAgB,KAAK,CAAC;IAC/B,MAAM,KAAK,GAAG,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC;SAC9B,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,SAAS,IAAI,OAAO,CAAC,KAAK,UAAU,CAAC;SAC7D,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,IAAI,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;IACxC,OAAO,IAAI,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC;AACjC,CAAC;AAED,SAAS,UAAU,CAAC,KAAY,EAAE,OAAiB,EAAE,OAAqB,EAAE,WAAoB;IAC9F,OAAO;QACL,WAAW,EAAE,WAAW,IAAI,aAAa,CAAC,KAAK,CAAC;QAChD,KAAK,EAAE,cAAc,CAAC,KAAK,CAAC;QAC5B,OAAO;QACP,OAAO;QACP,SAAS,EAAE,QAAQ;KACpB,CAAC;AACJ,CAAC;AAOD,MAAM,UAAU,sBAAsB,CACpC,KAA8B,EAC9B,IAA+B;IAE/B,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,uCAAuC,CAAC,CAAC;IACjF,IAAI,KAAK,GAAG,CAAC,CAAC;IACd,MAAM,IAAI,GAAG,KAAK,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAE,CAAC;IACtC,OAAO;QACL,QAAQ,EAAE,GAAG,EAAE;YACb,MAAM,IAAI,GAAG,KAAK,CAAC,KAAK,EAAE,CAAC,IAAI,IAAI,CAAC;YACpC,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC,OAAO,IAAI,EAAE,CAAC;YAClC,OAAO,IAAI,CAAC,KAAK,CAAC;QACpB,CAAC;QACD,UAAU,EAAE,KAAK,CAAC,MAAM;KACzB,CAAC;AACJ,CAAC;AAED,MAAM,OAAO,UAAU;IACJ,KAAK,GAAW,EAAE,CAAC;IAEpC,GAAG,CAAC,KAAY,EAAE,OAAiB,EAAE,UAAwB,EAAE;QAC7D,MAAM,IAAI,GAAG,UAAU,CAAC,KAAK,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC;QACjD,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACtB,OAAO,IAAI,CAAC;IACd,CAAC;IAED,WAAW,CAAC,IAAU;QACpB,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;QACrC,IAAI,GAAG,GAAG,CAAC,EAAE,CAAC;YACZ,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;YAC1B,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;QAC3B,CAAC;IACH,CAAC;IAED,UAAU,CAAC,OAAsC,EAAE,OAAiB,EAAE,WAAW,GAAG,WAAW;QAC7F,MAAM,IAAI,GAAG,UAAU,CAAC,OAAO,EAAE,OAAO,EAAE,EAAE,EAAE,WAAW,CAAC,CAAC;QAC3D,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACtB,OAAO,IAAI,CAAC;IACd,CAAC;IAED,KAAK,CAAC,GAAgB;QACpB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;YAC3C,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAE,CAAC;YAE5B,IAAI,IAAI,CAAC,SAAS,IAAI,CAAC;gBAAE,SAAS;YAClC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC;gBAAE,SAAS;YAE/B,IAAI,CAAC,SAAS,EAAE,CAAC;YACjB,IAAI,IAAI,CAAC,SAAS,IAAI,CAAC,EAAE,CAAC;gBACxB,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;YAC1B,CAAC;YACD,OAAO,IAAI,CAAC;QACd,CAAC;QACD,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,MAAM;QACJ,OAAO,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,SAAS,IAAI,CAAC,CAAC,CAAC;IACpF,CAAC;IAED,IAAI,SAAS;QACX,OAAO,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC;IAC3B,CAAC;IAED,QAAQ;QACN,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,WAAW,EAAE,CAAC,CAAC,WAAW,EAAE,SAAS,EAAE,CAAC,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC;IACzF,CAAC;IAED,KAAK;QACH,IAAI,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC;IACxB,CAAC;CACF"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { FastifyReply } from "fastify";
|
|
2
|
+
import type { SSEChunk } from "./formats/types.js";
|
|
3
|
+
import type { ReplyOptions } from "./types.js";
|
|
4
|
+
export declare function writeSSE(reply: FastifyReply, chunks: readonly SSEChunk[], options?: ReplyOptions): Promise<void>;
|
|
5
|
+
//# sourceMappingURL=sse-writer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sse-writer.d.ts","sourceRoot":"","sources":["../src/sse-writer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAC5C,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AACnD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAa/C,wBAAsB,QAAQ,CAC5B,KAAK,EAAE,YAAY,EACnB,MAAM,EAAE,SAAS,QAAQ,EAAE,EAC3B,OAAO,GAAE,YAAiB,GACzB,OAAO,CAAC,IAAI,CAAC,CAef"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
const HTTP_OK = 200;
|
|
2
|
+
function sleep(ms) {
|
|
3
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
4
|
+
}
|
|
5
|
+
function formatSSEChunk(chunk) {
|
|
6
|
+
const eventLine = chunk.event ? `event: ${chunk.event}\n` : "";
|
|
7
|
+
return `${eventLine}data: ${chunk.data}\n\n`;
|
|
8
|
+
}
|
|
9
|
+
export async function writeSSE(reply, chunks, options = {}) {
|
|
10
|
+
const latency = options.latency ?? 0;
|
|
11
|
+
reply.raw.writeHead(HTTP_OK, {
|
|
12
|
+
"Content-Type": "text/event-stream",
|
|
13
|
+
"Cache-Control": "no-cache",
|
|
14
|
+
Connection: "keep-alive",
|
|
15
|
+
});
|
|
16
|
+
for (const chunk of chunks) {
|
|
17
|
+
reply.raw.write(formatSSEChunk(chunk));
|
|
18
|
+
if (latency > 0)
|
|
19
|
+
await sleep(latency);
|
|
20
|
+
}
|
|
21
|
+
reply.raw.end();
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=sse-writer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sse-writer.js","sourceRoot":"","sources":["../src/sse-writer.ts"],"names":[],"mappings":"AAIA,MAAM,OAAO,GAAG,GAAG,CAAC;AAEpB,SAAS,KAAK,CAAC,EAAU;IACvB,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,CAAC;AAC3D,CAAC;AAED,SAAS,cAAc,CAAC,KAAe;IACrC,MAAM,SAAS,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,UAAU,KAAK,CAAC,KAAK,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC;IAC/D,OAAO,GAAG,SAAS,SAAS,KAAK,CAAC,IAAI,MAAM,CAAC;AAC/C,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,QAAQ,CAC5B,KAAmB,EACnB,MAA2B,EAC3B,UAAwB,EAAE;IAE1B,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,IAAI,CAAC,CAAC;IAErC,KAAK,CAAC,GAAG,CAAC,SAAS,CAAC,OAAO,EAAE;QAC3B,cAAc,EAAE,mBAAmB;QACnC,eAAe,EAAE,UAAU;QAC3B,UAAU,EAAE,YAAY;KACzB,CAAC,CAAC;IAEH,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QAC3B,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,cAAc,CAAC,KAAK,CAAC,CAAC,CAAC;QACvC,IAAI,OAAO,GAAG,CAAC;YAAE,MAAM,KAAK,CAAC,OAAO,CAAC,CAAC;IACxC,CAAC;IAED,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC;AAClB,CAAC"}
|
|
@@ -2,3 +2,4 @@ export type { FormatName, MockRequest, Message, ToolDef } from "./request.js";
|
|
|
2
2
|
export type { Reply, ReplyObject, ErrorReply, ToolCall, Resolver, ReplyOptions, SequenceEntry } from "./reply.js";
|
|
3
3
|
export type { Match, MatchObject, PendingRule, RuleHandle, RuleSummary, Handler, Rule } from "./rule.js";
|
|
4
4
|
export type { RecordedRequest } from "../history.js";
|
|
5
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,UAAU,EAAE,WAAW,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAC9E,YAAY,EAAE,KAAK,EAAE,WAAW,EAAE,UAAU,EAAE,QAAQ,EAAE,QAAQ,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAClH,YAAY,EAAE,KAAK,EAAE,WAAW,EAAE,WAAW,EAAE,UAAU,EAAE,WAAW,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACzG,YAAY,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { MockRequest } from "./request.js";
|
|
2
|
+
/** A reply is either a plain string (turns into `{ text: "..." }`) or a full reply object. */
|
|
3
|
+
export type Reply = string | ReplyObject;
|
|
4
|
+
/** A structured reply. Text, reasoning, tool calls, usage, and errors are all optional. */
|
|
5
|
+
export interface ReplyObject {
|
|
6
|
+
readonly text?: string | undefined;
|
|
7
|
+
/** Extended thinking or chain-of-thought. Works with Anthropic and Responses formats. */
|
|
8
|
+
readonly reasoning?: string | undefined;
|
|
9
|
+
readonly tools?: readonly ToolCall[] | undefined;
|
|
10
|
+
/** Falls back to `{ input: 10, output: 5 }` if omitted. */
|
|
11
|
+
readonly usage?: {
|
|
12
|
+
readonly input: number;
|
|
13
|
+
readonly output: number;
|
|
14
|
+
} | undefined;
|
|
15
|
+
/** When set, the server responds with this HTTP error instead of a normal reply. */
|
|
16
|
+
readonly error?: ErrorReply | undefined;
|
|
17
|
+
}
|
|
18
|
+
/** An HTTP error response. The server returns this status code with a format-appropriate body. */
|
|
19
|
+
export interface ErrorReply {
|
|
20
|
+
readonly status: number;
|
|
21
|
+
readonly message: string;
|
|
22
|
+
/** Each format has its own default if omitted. */
|
|
23
|
+
readonly type?: string | undefined;
|
|
24
|
+
}
|
|
25
|
+
export interface ToolCall {
|
|
26
|
+
/** Auto-generated if omitted. */
|
|
27
|
+
readonly id?: string | undefined;
|
|
28
|
+
readonly name: string;
|
|
29
|
+
readonly args: Readonly<Record<string, unknown>>;
|
|
30
|
+
}
|
|
31
|
+
/** A reply value or a function that produces one. Async functions are supported. */
|
|
32
|
+
export type Resolver = Reply | ((req: MockRequest) => Reply | Promise<Reply>);
|
|
33
|
+
/** Per-rule streaming options. Merged with server-level defaults, with per-rule values winning. */
|
|
34
|
+
export interface ReplyOptions {
|
|
35
|
+
/** Milliseconds between SSE chunks. */
|
|
36
|
+
readonly latency?: number | undefined;
|
|
37
|
+
/** Characters per SSE chunk for more realistic streaming. */
|
|
38
|
+
readonly chunkSize?: number | undefined;
|
|
39
|
+
}
|
|
40
|
+
/** A single entry in a reply sequence. Can be a plain reply or a reply with per-step options. */
|
|
41
|
+
export type SequenceEntry = Reply | {
|
|
42
|
+
readonly reply: Reply;
|
|
43
|
+
readonly options?: ReplyOptions;
|
|
44
|
+
};
|
|
45
|
+
//# sourceMappingURL=reply.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reply.d.ts","sourceRoot":"","sources":["../../src/types/reply.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAEhD,8FAA8F;AAC9F,MAAM,MAAM,KAAK,GAAG,MAAM,GAAG,WAAW,CAAC;AAEzC,2FAA2F;AAC3F,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACnC,yFAAyF;IACzF,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACxC,QAAQ,CAAC,KAAK,CAAC,EAAE,SAAS,QAAQ,EAAE,GAAG,SAAS,CAAC;IACjD,2DAA2D;IAC3D,QAAQ,CAAC,KAAK,CAAC,EAAE;QAAE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;KAAE,GAAG,SAAS,CAAC;IACjF,oFAAoF;IACpF,QAAQ,CAAC,KAAK,CAAC,EAAE,UAAU,GAAG,SAAS,CAAC;CACzC;AAED,kGAAkG;AAClG,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,kDAAkD;IAClD,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CACpC;AAED,MAAM,WAAW,QAAQ;IACvB,iCAAiC;IACjC,QAAQ,CAAC,EAAE,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACjC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;CAClD;AAED,oFAAoF;AACpF,MAAM,MAAM,QAAQ,GAAG,KAAK,GAAG,CAAC,CAAC,GAAG,EAAE,WAAW,KAAK,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC;AAE9E,mGAAmG;AACnG,MAAM,WAAW,YAAY;IAC3B,uCAAuC;IACvC,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACtC,6DAA6D;IAC7D,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CACzC;AAED,iGAAiG;AACjG,MAAM,MAAM,aAAa,GAAG,KAAK,GAAG;IAAE,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC;IAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,YAAY,CAAA;CAAE,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reply.js","sourceRoot":"","sources":["../../src/types/reply.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/** The LLM API wire format that was detected for a request. */
|
|
2
|
+
export type FormatName = "openai" | "anthropic" | "responses";
|
|
3
|
+
/** A normalised view of an incoming request, regardless of the original wire format. */
|
|
4
|
+
export interface MockRequest {
|
|
5
|
+
readonly format: FormatName;
|
|
6
|
+
readonly model: string;
|
|
7
|
+
readonly streaming: boolean;
|
|
8
|
+
/** Full conversation, normalised from whatever format came in. */
|
|
9
|
+
readonly messages: readonly Message[];
|
|
10
|
+
/** The last user message's text. This is what most matchers check. */
|
|
11
|
+
readonly lastMessage: string;
|
|
12
|
+
/** Empty string if there wasn't one. */
|
|
13
|
+
readonly systemMessage: string;
|
|
14
|
+
readonly tools?: readonly ToolDef[] | undefined;
|
|
15
|
+
/** Pulled out from `tools` for quick lookups. */
|
|
16
|
+
readonly toolNames: readonly string[];
|
|
17
|
+
/** Set when the last message was a tool result. */
|
|
18
|
+
readonly lastToolCallId: string | undefined;
|
|
19
|
+
/** The raw request body, for anything we don't extract. */
|
|
20
|
+
readonly raw: unknown;
|
|
21
|
+
readonly headers: Readonly<Record<string, string | undefined>>;
|
|
22
|
+
/** e.g. `/v1/chat/completions` */
|
|
23
|
+
readonly path: string;
|
|
24
|
+
}
|
|
25
|
+
/** A single conversation message, normalised across all supported formats. */
|
|
26
|
+
export interface Message {
|
|
27
|
+
readonly role: "system" | "user" | "assistant" | "tool";
|
|
28
|
+
readonly content: string;
|
|
29
|
+
/** Links the result back to its tool call. Only set on `"tool"` messages. */
|
|
30
|
+
readonly toolCallId?: string | undefined;
|
|
31
|
+
}
|
|
32
|
+
/** A tool definition from the request's `tools` array, normalised across formats. */
|
|
33
|
+
export interface ToolDef {
|
|
34
|
+
readonly name: string;
|
|
35
|
+
readonly description?: string | undefined;
|
|
36
|
+
/** JSON Schema, passed through as-is. */
|
|
37
|
+
readonly parameters?: unknown;
|
|
38
|
+
}
|
|
39
|
+
//# sourceMappingURL=request.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"request.d.ts","sourceRoot":"","sources":["../../src/types/request.ts"],"names":[],"mappings":"AAAA,+DAA+D;AAC/D,MAAM,MAAM,UAAU,GAAG,QAAQ,GAAG,WAAW,GAAG,WAAW,CAAC;AAE9D,wFAAwF;AACxF,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,MAAM,EAAE,UAAU,CAAC;IAC5B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;IAC5B,kEAAkE;IAClE,QAAQ,CAAC,QAAQ,EAAE,SAAS,OAAO,EAAE,CAAC;IACtC,sEAAsE;IACtE,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,wCAAwC;IACxC,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,KAAK,CAAC,EAAE,SAAS,OAAO,EAAE,GAAG,SAAS,CAAC;IAChD,iDAAiD;IACjD,QAAQ,CAAC,SAAS,EAAE,SAAS,MAAM,EAAE,CAAC;IACtC,mDAAmD;IACnD,QAAQ,CAAC,cAAc,EAAE,MAAM,GAAG,SAAS,CAAC;IAC5C,2DAA2D;IAC3D,QAAQ,CAAC,GAAG,EAAE,OAAO,CAAC;IACtB,QAAQ,CAAC,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC,CAAC;IAC/D,kCAAkC;IAClC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB;AAED,8EAA8E;AAC9E,MAAM,WAAW,OAAO;IACtB,QAAQ,CAAC,IAAI,EAAE,QAAQ,GAAG,MAAM,GAAG,WAAW,GAAG,MAAM,CAAC;IACxD,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,6EAA6E;IAC7E,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAC1C;AAED,qFAAqF;AACrF,MAAM,WAAW,OAAO;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1C,yCAAyC;IACzC,QAAQ,CAAC,UAAU,CAAC,EAAE,OAAO,CAAC;CAC/B"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"request.js","sourceRoot":"","sources":["../../src/types/request.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import type { MockRequest, FormatName } from "./request.js";
|
|
2
|
+
import type { Resolver, ReplyOptions, Reply, SequenceEntry } from "./reply.js";
|
|
3
|
+
/**
|
|
4
|
+
* Determines whether a rule matches an incoming request.
|
|
5
|
+
*
|
|
6
|
+
* A `string` does a case-insensitive substring match on the last user message.
|
|
7
|
+
* A `RegExp` gets tested against the last user message.
|
|
8
|
+
* A `MatchObject` checks multiple fields at once with AND logic.
|
|
9
|
+
* A function receives the normalised request and returns a boolean.
|
|
10
|
+
*/
|
|
11
|
+
export type Match = string | RegExp | MatchObject | ((req: MockRequest) => boolean);
|
|
12
|
+
/** A structured matcher. Every field you set must match for the rule to fire. */
|
|
13
|
+
export interface MatchObject {
|
|
14
|
+
readonly message?: string | RegExp;
|
|
15
|
+
readonly model?: string | RegExp;
|
|
16
|
+
readonly system?: string | RegExp;
|
|
17
|
+
readonly format?: FormatName;
|
|
18
|
+
/** Match when the request includes a tool definition with this name. */
|
|
19
|
+
readonly toolName?: string;
|
|
20
|
+
/** Match when the last tool-result message has this `tool_call_id`. */
|
|
21
|
+
readonly toolCallId?: string;
|
|
22
|
+
/** Extra check that runs after all other fields pass. */
|
|
23
|
+
readonly predicate?: (req: MockRequest) => boolean;
|
|
24
|
+
}
|
|
25
|
+
/** Returned by `when()`. Call `.reply()` or `.replySequence()` on it to complete the rule. */
|
|
26
|
+
export interface PendingRule {
|
|
27
|
+
reply(response: Resolver, options?: ReplyOptions): RuleHandle;
|
|
28
|
+
/** Each match advances through the array. The last entry repeats once exhausted. */
|
|
29
|
+
replySequence(entries: readonly SequenceEntry[]): RuleHandle;
|
|
30
|
+
}
|
|
31
|
+
/** A handle to a registered rule. All methods return `this` for chaining. */
|
|
32
|
+
export interface RuleHandle {
|
|
33
|
+
times(n: number): RuleHandle;
|
|
34
|
+
first(): RuleHandle;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* The shape of a handler file's default export.
|
|
38
|
+
* You can export a single handler or an array of them.
|
|
39
|
+
*/
|
|
40
|
+
export interface Handler {
|
|
41
|
+
match: (req: MockRequest) => boolean;
|
|
42
|
+
respond: (req: MockRequest) => Reply | Promise<Reply>;
|
|
43
|
+
}
|
|
44
|
+
/** A summary of a registered rule, for inspection. */
|
|
45
|
+
export interface RuleSummary {
|
|
46
|
+
readonly description: string;
|
|
47
|
+
/** `Infinity` means unlimited. */
|
|
48
|
+
readonly remaining: number;
|
|
49
|
+
}
|
|
50
|
+
export interface Rule {
|
|
51
|
+
readonly description: string;
|
|
52
|
+
readonly match: (req: MockRequest) => boolean;
|
|
53
|
+
resolve: Resolver;
|
|
54
|
+
options: ReplyOptions;
|
|
55
|
+
remaining: number;
|
|
56
|
+
}
|
|
57
|
+
//# sourceMappingURL=rule.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rule.d.ts","sourceRoot":"","sources":["../../src/types/rule.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC5D,OAAO,KAAK,EAAE,QAAQ,EAAE,YAAY,EAAE,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAE/E;;;;;;;GAOG;AACH,MAAM,MAAM,KAAK,GACb,MAAM,GACN,MAAM,GACN,WAAW,GACX,CAAC,CAAC,GAAG,EAAE,WAAW,KAAK,OAAO,CAAC,CAAC;AAEpC,iFAAiF;AACjF,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACnC,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACjC,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAClC,QAAQ,CAAC,MAAM,CAAC,EAAE,UAAU,CAAC;IAC7B,wEAAwE;IACxE,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,uEAAuE;IACvE,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B,yDAAyD;IACzD,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC,GAAG,EAAE,WAAW,KAAK,OAAO,CAAC;CACpD;AAED,8FAA8F;AAC9F,MAAM,WAAW,WAAW;IAC1B,KAAK,CAAC,QAAQ,EAAE,QAAQ,EAAE,OAAO,CAAC,EAAE,YAAY,GAAG,UAAU,CAAC;IAC9D,oFAAoF;IACpF,aAAa,CAAC,OAAO,EAAE,SAAS,aAAa,EAAE,GAAG,UAAU,CAAC;CAC9D;AAED,6EAA6E;AAC7E,MAAM,WAAW,UAAU;IACzB,KAAK,CAAC,CAAC,EAAE,MAAM,GAAG,UAAU,CAAC;IAC7B,KAAK,IAAI,UAAU,CAAC;CACrB;AAED;;;GAGG;AACH,MAAM,WAAW,OAAO;IACtB,KAAK,EAAE,CAAC,GAAG,EAAE,WAAW,KAAK,OAAO,CAAC;IACrC,OAAO,EAAE,CAAC,GAAG,EAAE,WAAW,KAAK,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC;CACvD;AAED,sDAAsD;AACtD,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,kCAAkC;IAClC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC5B;AAED,MAAM,WAAW,IAAI;IACnB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,KAAK,EAAE,CAAC,GAAG,EAAE,WAAW,KAAK,OAAO,CAAC;IAC9C,OAAO,EAAE,QAAQ,CAAC;IAClB,OAAO,EAAE,YAAY,CAAC;IACtB,SAAS,EAAE,MAAM,CAAC;CACnB"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rule.js","sourceRoot":"","sources":["../../src/types/rule.ts"],"names":[],"mappings":""}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export type { FormatName, MockRequest, Message, ToolDef } from "./types/request.js";
|
|
2
|
+
export type { Reply, ReplyObject, ErrorReply, ToolCall, Resolver, ReplyOptions, SequenceEntry } from "./types/reply.js";
|
|
3
|
+
export type { Match, MatchObject, PendingRule, RuleHandle, RuleSummary, Handler, Rule } from "./types/rule.js";
|
|
4
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,UAAU,EAAE,WAAW,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AACpF,YAAY,EAAE,KAAK,EAAE,WAAW,EAAE,UAAU,EAAE,QAAQ,EAAE,QAAQ,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACxH,YAAY,EAAE,KAAK,EAAE,WAAW,EAAE,WAAW,EAAE,UAAU,EAAE,WAAW,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAC"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "llm-mock-server",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"description": "A standalone mock LLM server for deterministic testing: OpenAI, Anthropic, and Responses API formats",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
|
@@ -41,6 +41,7 @@
|
|
|
41
41
|
"commander": "14.0.3",
|
|
42
42
|
"fastify": "5.8.2",
|
|
43
43
|
"json5": "2.2.3",
|
|
44
|
+
"llm-schemas": "1.0.1",
|
|
44
45
|
"picocolors": "1.1.1",
|
|
45
46
|
"zod": "4.3.6"
|
|
46
47
|
},
|
package/scorecard.png
ADDED
|
Binary file
|
package/src/cli-validators.ts
CHANGED
|
@@ -28,6 +28,9 @@ export function parseLogLevel(value: string): LogLevel {
|
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
export async function parseHost(value: string): Promise<string> {
|
|
31
|
+
if (!value) {
|
|
32
|
+
throw new Error(`Invalid host "${value}". Must be a resolvable hostname or IP address.`);
|
|
33
|
+
}
|
|
31
34
|
if (value === "localhost" || isIP(value) !== 0) {
|
|
32
35
|
return value;
|
|
33
36
|
}
|
package/src/cli.ts
CHANGED
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
import { watch } from "node:fs";
|
|
4
|
+
import { createRequire } from "node:module";
|
|
4
5
|
import { Command } from "commander";
|
|
5
6
|
import pc from "picocolors";
|
|
6
7
|
import { MockServer } from "./mock-server.js";
|
|
7
8
|
import { Logger } from "./logger.js";
|
|
8
9
|
import { parsePort, parseHost, parseLogLevel, parseChunkSize, parseLatency } from "./cli-validators.js";
|
|
9
10
|
|
|
11
|
+
const require = createRequire(import.meta.url);
|
|
12
|
+
const { version } = require("../package.json") as { version: string };
|
|
13
|
+
|
|
10
14
|
const WATCH_DEBOUNCE_MS = 100;
|
|
11
15
|
|
|
12
16
|
interface StartOptions {
|
|
@@ -54,7 +58,7 @@ async function start(options: StartOptions): Promise<void> {
|
|
|
54
58
|
|
|
55
59
|
if (!quiet) {
|
|
56
60
|
console.log();
|
|
57
|
-
console.log(` ${pc.bold(pc.cyan("llm-mock-server"))} ${pc.dim(
|
|
61
|
+
console.log(` ${pc.bold(pc.cyan("llm-mock-server"))} ${pc.dim(`v${version}`)}`);
|
|
58
62
|
console.log();
|
|
59
63
|
console.log(` ${pc.dim("Port")} ${pc.bold(String(port))}`);
|
|
60
64
|
console.log(` ${pc.dim("Rules")} ${pc.bold(String(server.ruleCount))} loaded`);
|
|
@@ -106,7 +110,7 @@ async function start(options: StartOptions): Promise<void> {
|
|
|
106
110
|
const program = new Command()
|
|
107
111
|
.name("llm-mock-server")
|
|
108
112
|
.description("Mock LLM server for deterministic testing")
|
|
109
|
-
.version(
|
|
113
|
+
.version(version);
|
|
110
114
|
|
|
111
115
|
program
|
|
112
116
|
.command("start", { isDefault: true })
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Format } from "../types.js";
|
|
2
|
-
import { isStreaming } from "../
|
|
2
|
+
import { isStreaming } from "../request-helpers.js";
|
|
3
3
|
import { parseRequest } from "./parse.js";
|
|
4
4
|
import { serialize, serializeComplete, serializeError } from "./serialize.js";
|
|
5
5
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { MockRequest, Message, ToolDef } from "../../types.js";
|
|
2
|
-
import { buildMockRequest, type RequestMeta } from "../
|
|
2
|
+
import { buildMockRequest, type RequestMeta } from "../request-helpers.js";
|
|
3
3
|
import { AnthropicRequestSchema, type AnthropicRequest } from "./schema.js";
|
|
4
4
|
|
|
5
5
|
function extractSystem(system: AnthropicRequest["system"]): Message[] {
|
|
@@ -12,11 +12,11 @@ function extractSystem(system: AnthropicRequest["system"]): Message[] {
|
|
|
12
12
|
function extractContent(content: AnthropicRequest["messages"][number]["content"]): { content: string; toolCallId?: string | undefined } {
|
|
13
13
|
if (typeof content === "string") return { content };
|
|
14
14
|
const text = content
|
|
15
|
-
.filter((b) => b.type === "text")
|
|
15
|
+
.filter((b): b is { type: "text"; text: string } => b.type === "text")
|
|
16
16
|
.map((b) => b.text)
|
|
17
17
|
.join("\n");
|
|
18
|
-
const toolResult = content.find((b) => b.type === "tool_result");
|
|
19
|
-
const toolCallId = toolResult?.
|
|
18
|
+
const toolResult = content.find((b): b is { type: "tool_result"; tool_use_id: string } => b.type === "tool_result");
|
|
19
|
+
const toolCallId = toolResult?.tool_use_id;
|
|
20
20
|
return { content: text, toolCallId };
|
|
21
21
|
}
|
|
22
22
|
|
|
@@ -1,73 +1,6 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
const ToolUseBlockSchema = z.object({
|
|
6
|
-
type: z.literal("tool_use"),
|
|
7
|
-
id: z.string(),
|
|
8
|
-
name: z.string(),
|
|
9
|
-
input: z.record(z.string(), z.unknown()),
|
|
10
|
-
});
|
|
11
|
-
|
|
12
|
-
const ToolResultBlockSchema = z.object({
|
|
13
|
-
type: z.literal("tool_result"),
|
|
14
|
-
tool_use_id: z.string(),
|
|
15
|
-
content: z.union([z.string(), z.array(TextBlockSchema)]).optional(),
|
|
16
|
-
});
|
|
17
|
-
|
|
18
|
-
const KnownContentBlockSchema = z.discriminatedUnion("type", [
|
|
19
|
-
TextBlockSchema,
|
|
20
|
-
ToolUseBlockSchema,
|
|
21
|
-
ToolResultBlockSchema,
|
|
22
|
-
]);
|
|
23
|
-
|
|
24
|
-
const LooseContentBlockSchema = z.union([
|
|
25
|
-
KnownContentBlockSchema,
|
|
26
|
-
z.looseObject({ type: z.string() }),
|
|
27
|
-
]);
|
|
28
|
-
|
|
29
|
-
const KNOWN_BLOCK_TYPES = new Set(["text", "tool_use", "tool_result"]);
|
|
30
|
-
|
|
31
|
-
type KnownBlock = z.infer<typeof KnownContentBlockSchema>;
|
|
32
|
-
|
|
33
|
-
const MessageSchema = z.object({
|
|
34
|
-
role: z.enum(["user", "assistant"]),
|
|
35
|
-
content: z.union([
|
|
36
|
-
z.string(),
|
|
37
|
-
z.array(LooseContentBlockSchema).transform((blocks) =>
|
|
38
|
-
blocks.filter((b): b is KnownBlock => KNOWN_BLOCK_TYPES.has(b.type)),
|
|
39
|
-
),
|
|
40
|
-
]),
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
const ToolDefinitionSchema = z.object({
|
|
44
|
-
name: z.string(),
|
|
45
|
-
description: z.string().optional(),
|
|
46
|
-
input_schema: z.record(z.string(), z.unknown()),
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
export const AnthropicRequestSchema = z.looseObject({
|
|
50
|
-
model: z.string().min(1),
|
|
51
|
-
max_tokens: z.number().int().positive(),
|
|
52
|
-
system: z.union([z.string(), z.array(TextBlockSchema)]).optional(),
|
|
53
|
-
messages: z.array(MessageSchema).min(1),
|
|
54
|
-
tools: z.array(ToolDefinitionSchema).optional(),
|
|
55
|
-
stream: z.boolean().optional(),
|
|
56
|
-
temperature: z.number().optional(),
|
|
57
|
-
top_p: z.number().optional(),
|
|
58
|
-
top_k: z.number().optional(),
|
|
59
|
-
stop_sequences: z.array(z.string()).optional(),
|
|
60
|
-
metadata: z.record(z.string(), z.unknown()).optional(),
|
|
61
|
-
cache_control: z.unknown().optional(),
|
|
62
|
-
container: z.string().optional(),
|
|
63
|
-
inference_geo: z.string().optional(),
|
|
64
|
-
output_config: z.unknown().optional(),
|
|
65
|
-
service_tier: z.string().optional(),
|
|
66
|
-
thinking: z.unknown().optional(),
|
|
67
|
-
tool_choice: z.unknown().optional(),
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
export type AnthropicRequest = z.infer<typeof AnthropicRequestSchema>;
|
|
3
|
+
export { AnthropicRequestSchema, type AnthropicRequest } from "llm-schemas/anthropic";
|
|
71
4
|
|
|
72
5
|
const ResponseContentBlockSchema = z.object({
|
|
73
6
|
type: z.string(),
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import type { ReplyObject, ReplyOptions } from "../../types.js";
|
|
2
2
|
import type { SSEChunk } from "../types.js";
|
|
3
|
-
import { splitText, genId, toolId, shouldEmitText, finishReason, DEFAULT_USAGE } from "../
|
|
3
|
+
import { splitText, genId, toolId, shouldEmitText, finishReason, DEFAULT_USAGE } from "../serialize-helpers.js";
|
|
4
|
+
|
|
5
|
+
function buildUsage(usage: { input: number; output: number }) {
|
|
6
|
+
return { input_tokens: usage.input, output_tokens: usage.output };
|
|
7
|
+
}
|
|
4
8
|
|
|
5
9
|
function contentBlock(index: number, startBlock: unknown, deltas: SSEChunk[]): SSEChunk[] {
|
|
6
10
|
return [
|
|
@@ -52,7 +56,7 @@ export function serialize(reply: ReplyObject, model: string, options: ReplyOptio
|
|
|
52
56
|
return [
|
|
53
57
|
{ event: "message_start", data: JSON.stringify({
|
|
54
58
|
type: "message_start",
|
|
55
|
-
message: { id, type: "message", role: "assistant", model, content: [], stop_reason: null, usage: {
|
|
59
|
+
message: { id, type: "message", role: "assistant", model, content: [], stop_reason: null, usage: { ...buildUsage(usage), output_tokens: 0 } },
|
|
56
60
|
})},
|
|
57
61
|
...reasoningChunks,
|
|
58
62
|
...textChunks,
|
|
@@ -66,7 +70,7 @@ export function serialize(reply: ReplyObject, model: string, options: ReplyOptio
|
|
|
66
70
|
];
|
|
67
71
|
}
|
|
68
72
|
|
|
69
|
-
export function serializeComplete(reply: ReplyObject, model: string): unknown {
|
|
73
|
+
export function serializeComplete(reply: ReplyObject, model: string): Record<string, unknown> {
|
|
70
74
|
const id = genId("msg");
|
|
71
75
|
const usage = reply.usage ?? DEFAULT_USAGE;
|
|
72
76
|
|
|
@@ -82,10 +86,10 @@ export function serializeComplete(reply: ReplyObject, model: string): unknown {
|
|
|
82
86
|
id, type: "message", role: "assistant", model, content,
|
|
83
87
|
stop_reason: finishReason(reply, "tool_use", "end_turn"),
|
|
84
88
|
stop_sequence: null,
|
|
85
|
-
usage:
|
|
89
|
+
usage: buildUsage(usage),
|
|
86
90
|
};
|
|
87
91
|
}
|
|
88
92
|
|
|
89
|
-
export function serializeError(error: { status: number; message: string; type?: string }): unknown {
|
|
93
|
+
export function serializeError(error: { status: number; message: string; type?: string }): Record<string, unknown> {
|
|
90
94
|
return { type: "error", error: { type: error.type ?? "api_error", message: error.message } };
|
|
91
95
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Format } from "../types.js";
|
|
2
|
-
import { isStreaming } from "../
|
|
2
|
+
import { isStreaming } from "../request-helpers.js";
|
|
3
3
|
import { parseRequest } from "./parse.js";
|
|
4
4
|
import { serialize, serializeComplete, serializeError } from "./serialize.js";
|
|
5
5
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { MockRequest, Message, ToolDef } from "../../types.js";
|
|
2
|
-
import { buildMockRequest, type RequestMeta } from "../
|
|
2
|
+
import { buildMockRequest, type RequestMeta } from "../request-helpers.js";
|
|
3
3
|
import { OpenAIRequestSchema, type OpenAIRequest } from "./schema.js";
|
|
4
4
|
|
|
5
5
|
function extractContent(content: OpenAIRequest["messages"][number]["content"]): string {
|
|
@@ -1,74 +1,6 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
type: z.string(),
|
|
5
|
-
text: z.string().optional(),
|
|
6
|
-
});
|
|
7
|
-
|
|
8
|
-
const MessageSchema = z.object({
|
|
9
|
-
role: z.enum(["system", "developer", "user", "assistant", "tool"]).optional(),
|
|
10
|
-
content: z.union([z.string(), z.array(ContentPartSchema), z.null()]).optional(),
|
|
11
|
-
name: z.string().optional(),
|
|
12
|
-
tool_calls: z.array(z.object({
|
|
13
|
-
index: z.number().optional(),
|
|
14
|
-
id: z.string().optional(),
|
|
15
|
-
type: z.string().optional(),
|
|
16
|
-
function: z.object({
|
|
17
|
-
name: z.string(),
|
|
18
|
-
arguments: z.string(),
|
|
19
|
-
}),
|
|
20
|
-
})).optional(),
|
|
21
|
-
tool_call_id: z.string().optional(),
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
const ToolSchema = z.object({
|
|
25
|
-
type: z.string(),
|
|
26
|
-
function: z.object({
|
|
27
|
-
name: z.string(),
|
|
28
|
-
description: z.string().optional(),
|
|
29
|
-
parameters: z.record(z.string(), z.unknown()).optional(),
|
|
30
|
-
}),
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
export const OpenAIRequestSchema = z.looseObject({
|
|
34
|
-
model: z.string().min(1),
|
|
35
|
-
messages: z.array(MessageSchema).min(1),
|
|
36
|
-
temperature: z.number().optional(),
|
|
37
|
-
top_p: z.number().optional(),
|
|
38
|
-
n: z.number().optional(),
|
|
39
|
-
stop: z.union([z.string(), z.array(z.string())]).optional(),
|
|
40
|
-
max_tokens: z.number().optional(),
|
|
41
|
-
presence_penalty: z.number().optional(),
|
|
42
|
-
frequency_penalty: z.number().optional(),
|
|
43
|
-
tools: z.array(ToolSchema).optional(),
|
|
44
|
-
stream: z.boolean().optional(),
|
|
45
|
-
tool_choice: z.unknown().optional(),
|
|
46
|
-
user: z.string().optional(),
|
|
47
|
-
audio: z.unknown().optional(),
|
|
48
|
-
function_call: z.unknown().optional(),
|
|
49
|
-
functions: z.array(z.unknown()).optional(),
|
|
50
|
-
logit_bias: z.record(z.string(), z.number()).optional(),
|
|
51
|
-
logprobs: z.boolean().optional(),
|
|
52
|
-
max_completion_tokens: z.number().optional(),
|
|
53
|
-
metadata: z.record(z.string(), z.string()).optional(),
|
|
54
|
-
modalities: z.array(z.string()).optional(),
|
|
55
|
-
parallel_tool_calls: z.boolean().optional(),
|
|
56
|
-
prediction: z.unknown().optional(),
|
|
57
|
-
prompt_cache_key: z.string().optional(),
|
|
58
|
-
prompt_cache_retention: z.string().optional(),
|
|
59
|
-
reasoning_effort: z.string().optional(),
|
|
60
|
-
response_format: z.unknown().optional(),
|
|
61
|
-
safety_identifier: z.string().optional(),
|
|
62
|
-
seed: z.number().optional(),
|
|
63
|
-
service_tier: z.string().optional(),
|
|
64
|
-
store: z.boolean().optional(),
|
|
65
|
-
stream_options: z.unknown().optional(),
|
|
66
|
-
top_logprobs: z.number().optional(),
|
|
67
|
-
verbosity: z.string().optional(),
|
|
68
|
-
web_search_options: z.unknown().optional(),
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
export type OpenAIRequest = z.infer<typeof OpenAIRequestSchema>;
|
|
3
|
+
export { OpenAIRequestSchema, type OpenAIRequest } from "llm-schemas/openai/chat-completions";
|
|
72
4
|
|
|
73
5
|
const ToolCallResponseSchema = z.object({
|
|
74
6
|
id: z.string(),
|
|
@@ -1,6 +1,16 @@
|
|
|
1
1
|
import type { ReplyObject, ReplyOptions } from "../../types.js";
|
|
2
2
|
import type { SSEChunk } from "../types.js";
|
|
3
|
-
import { splitText, genId, toolId, finishReason, MS_PER_SECOND, DEFAULT_USAGE } from "../
|
|
3
|
+
import { splitText, genId, toolId, finishReason, MS_PER_SECOND, DEFAULT_USAGE } from "../serialize-helpers.js";
|
|
4
|
+
|
|
5
|
+
function buildUsage(usage: { input: number; output: number }) {
|
|
6
|
+
return {
|
|
7
|
+
prompt_tokens: usage.input,
|
|
8
|
+
completion_tokens: usage.output,
|
|
9
|
+
total_tokens: usage.input + usage.output,
|
|
10
|
+
prompt_tokens_details: { cached_tokens: 0, audio_tokens: 0 },
|
|
11
|
+
completion_tokens_details: { reasoning_tokens: 0, audio_tokens: 0, accepted_prediction_tokens: 0, rejected_prediction_tokens: 0 },
|
|
12
|
+
};
|
|
13
|
+
}
|
|
4
14
|
|
|
5
15
|
function chunkEnvelope(
|
|
6
16
|
id: string, created: number, model: string,
|
|
@@ -38,13 +48,7 @@ export function serialize(reply: ReplyObject, model: string, options: ReplyOptio
|
|
|
38
48
|
}),
|
|
39
49
|
);
|
|
40
50
|
|
|
41
|
-
const usageChunk =
|
|
42
|
-
prompt_tokens: usage.input,
|
|
43
|
-
completion_tokens: usage.output,
|
|
44
|
-
total_tokens: usage.input + usage.output,
|
|
45
|
-
prompt_tokens_details: { cached_tokens: 0, audio_tokens: 0 },
|
|
46
|
-
completion_tokens_details: { reasoning_tokens: 0, audio_tokens: 0, accepted_prediction_tokens: 0, rejected_prediction_tokens: 0 },
|
|
47
|
-
};
|
|
51
|
+
const usageChunk = buildUsage(usage);
|
|
48
52
|
|
|
49
53
|
return [
|
|
50
54
|
chunkEnvelope(id, created, model, { role: "assistant" }),
|
|
@@ -56,7 +60,7 @@ export function serialize(reply: ReplyObject, model: string, options: ReplyOptio
|
|
|
56
60
|
];
|
|
57
61
|
}
|
|
58
62
|
|
|
59
|
-
export function serializeComplete(reply: ReplyObject, model: string): unknown {
|
|
63
|
+
export function serializeComplete(reply: ReplyObject, model: string): Record<string, unknown> {
|
|
60
64
|
const id = genId("chatcmpl");
|
|
61
65
|
const created = Math.floor(Date.now() / MS_PER_SECOND);
|
|
62
66
|
const usage = reply.usage ?? DEFAULT_USAGE;
|
|
@@ -77,16 +81,10 @@ export function serializeComplete(reply: ReplyObject, model: string): unknown {
|
|
|
77
81
|
system_fingerprint: null,
|
|
78
82
|
service_tier: "default",
|
|
79
83
|
choices: [{ index: 0, message, logprobs: null, finish_reason: finishReason(reply, "tool_calls", "stop") }],
|
|
80
|
-
usage:
|
|
81
|
-
prompt_tokens: usage.input,
|
|
82
|
-
completion_tokens: usage.output,
|
|
83
|
-
total_tokens: usage.input + usage.output,
|
|
84
|
-
prompt_tokens_details: { cached_tokens: 0, audio_tokens: 0 },
|
|
85
|
-
completion_tokens_details: { reasoning_tokens: 0, audio_tokens: 0, accepted_prediction_tokens: 0, rejected_prediction_tokens: 0 },
|
|
86
|
-
},
|
|
84
|
+
usage: buildUsage(usage),
|
|
87
85
|
};
|
|
88
86
|
}
|
|
89
87
|
|
|
90
|
-
export function serializeError(error: { status: number; message: string; type?: string }): unknown {
|
|
88
|
+
export function serializeError(error: { status: number; message: string; type?: string }): Record<string, unknown> {
|
|
91
89
|
return { error: { message: error.message, type: error.type ?? "server_error", code: null } };
|
|
92
90
|
}
|