@copilotkit/aimock 1.44.0 → 1.45.1
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 +42 -0
- package/README.md +6 -0
- package/dist/agui-types.d.cts.map +1 -1
- package/dist/aimock-cli.cjs +31 -5
- package/dist/aimock-cli.cjs.map +1 -1
- package/dist/aimock-cli.d.cts.map +1 -1
- package/dist/aimock-cli.d.ts.map +1 -1
- package/dist/aimock-cli.js +31 -5
- package/dist/aimock-cli.js.map +1 -1
- package/dist/bedrock-converse.cjs +312 -39
- 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 +313 -40
- package/dist/bedrock-converse.js.map +1 -1
- package/dist/bedrock.cjs +195 -42
- 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 +196 -43
- package/dist/bedrock.js.map +1 -1
- package/dist/chaos.cjs +1 -0
- package/dist/chaos.js +1 -1
- package/dist/cli.cjs +152 -5
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +155 -8
- package/dist/cli.js.map +1 -1
- package/dist/cohere.cjs +132 -71
- package/dist/cohere.cjs.map +1 -1
- package/dist/cohere.d.cts.map +1 -1
- package/dist/cohere.d.ts.map +1 -1
- package/dist/cohere.js +133 -72
- package/dist/cohere.js.map +1 -1
- package/dist/config-loader.cjs +62 -4
- package/dist/config-loader.cjs.map +1 -1
- package/dist/config-loader.d.cts +37 -4
- package/dist/config-loader.d.cts.map +1 -1
- package/dist/config-loader.d.ts +37 -4
- package/dist/config-loader.d.ts.map +1 -1
- package/dist/config-loader.js +62 -4
- package/dist/config-loader.js.map +1 -1
- package/dist/fakes-plugin.cjs +131 -0
- package/dist/fakes-plugin.cjs.map +1 -0
- package/dist/fakes-plugin.d.cts +23 -0
- package/dist/fakes-plugin.d.cts.map +1 -0
- package/dist/fakes-plugin.d.ts +23 -0
- package/dist/fakes-plugin.d.ts.map +1 -0
- package/dist/fakes-plugin.js +127 -0
- package/dist/fakes-plugin.js.map +1 -0
- package/dist/fixture-loader-services.cjs +1 -1
- package/dist/fixture-loader-services.cjs.map +1 -1
- package/dist/fixture-loader-services.js +1 -1
- package/dist/fixture-loader-services.js.map +1 -1
- package/dist/fixture-loader.cjs +389 -27
- package/dist/fixture-loader.cjs.map +1 -1
- package/dist/fixture-loader.d.cts +47 -3
- package/dist/fixture-loader.d.cts.map +1 -1
- package/dist/fixture-loader.d.ts +47 -3
- package/dist/fixture-loader.d.ts.map +1 -1
- package/dist/fixture-loader.js +384 -29
- package/dist/fixture-loader.js.map +1 -1
- package/dist/gemini-interactions.cjs +250 -52
- package/dist/gemini-interactions.cjs.map +1 -1
- package/dist/gemini-interactions.d.cts.map +1 -1
- package/dist/gemini-interactions.d.ts.map +1 -1
- package/dist/gemini-interactions.js +251 -53
- package/dist/gemini-interactions.js.map +1 -1
- package/dist/gemini.cjs +226 -37
- package/dist/gemini.cjs.map +1 -1
- package/dist/gemini.d.cts.map +1 -1
- package/dist/gemini.d.ts.map +1 -1
- package/dist/gemini.js +227 -38
- package/dist/gemini.js.map +1 -1
- package/dist/helpers.cjs +881 -7
- package/dist/helpers.cjs.map +1 -1
- package/dist/helpers.d.cts +85 -4
- package/dist/helpers.d.cts.map +1 -1
- package/dist/helpers.d.ts +85 -4
- package/dist/helpers.d.ts.map +1 -1
- package/dist/helpers.js +839 -8
- package/dist/helpers.js.map +1 -1
- package/dist/index.cjs +9 -0
- package/dist/index.d.cts +7 -5
- package/dist/index.d.ts +7 -5
- package/dist/index.js +4 -3
- package/dist/jest.cjs +31 -7
- package/dist/jest.cjs.map +1 -1
- package/dist/jest.d.cts +43 -3
- package/dist/jest.d.cts.map +1 -1
- package/dist/jest.d.ts +43 -3
- package/dist/jest.d.ts.map +1 -1
- package/dist/jest.js +33 -9
- package/dist/jest.js.map +1 -1
- package/dist/journal.cjs +135 -13
- package/dist/journal.cjs.map +1 -1
- package/dist/journal.d.cts +38 -4
- package/dist/journal.d.cts.map +1 -1
- package/dist/journal.d.ts +38 -4
- package/dist/journal.d.ts.map +1 -1
- package/dist/journal.js +135 -13
- package/dist/journal.js.map +1 -1
- package/dist/live-sanitize.cjs +4 -32
- package/dist/live-sanitize.cjs.map +1 -1
- package/dist/live-sanitize.js +4 -32
- package/dist/live-sanitize.js.map +1 -1
- package/dist/llmock.cjs +123 -30
- package/dist/llmock.cjs.map +1 -1
- package/dist/llmock.d.cts +16 -1
- package/dist/llmock.d.cts.map +1 -1
- package/dist/llmock.d.ts +16 -1
- package/dist/llmock.d.ts.map +1 -1
- package/dist/llmock.js +125 -32
- package/dist/llmock.js.map +1 -1
- package/dist/mcp-fakes-mount.cjs +1 -0
- package/dist/mcp-fakes-mount.cjs.map +1 -1
- package/dist/mcp-fakes-mount.js +1 -0
- package/dist/mcp-fakes-mount.js.map +1 -1
- package/dist/mcp-fakes-report.cjs +111 -0
- package/dist/mcp-fakes-report.cjs.map +1 -0
- package/dist/mcp-fakes-report.d.cts +40 -0
- package/dist/mcp-fakes-report.d.cts.map +1 -0
- package/dist/mcp-fakes-report.d.ts +40 -0
- package/dist/mcp-fakes-report.d.ts.map +1 -0
- package/dist/mcp-fakes-report.js +107 -0
- package/dist/mcp-fakes-report.js.map +1 -0
- package/dist/mcp-fakes.cjs +331 -8
- package/dist/mcp-fakes.cjs.map +1 -1
- package/dist/mcp-fakes.d.cts +406 -3
- package/dist/mcp-fakes.d.cts.map +1 -1
- package/dist/mcp-fakes.d.ts +406 -3
- package/dist/mcp-fakes.d.ts.map +1 -1
- package/dist/mcp-fakes.js +331 -8
- package/dist/mcp-fakes.js.map +1 -1
- package/dist/mcp-handler.cjs +216 -17
- package/dist/mcp-handler.cjs.map +1 -1
- package/dist/mcp-handler.d.cts +49 -0
- package/dist/mcp-handler.d.cts.map +1 -0
- package/dist/mcp-handler.d.ts +49 -0
- package/dist/mcp-handler.d.ts.map +1 -0
- package/dist/mcp-handler.js +214 -18
- package/dist/mcp-handler.js.map +1 -1
- package/dist/mcp-mock.cjs +112 -1
- package/dist/mcp-mock.cjs.map +1 -1
- package/dist/mcp-mock.d.cts +33 -2
- package/dist/mcp-mock.d.cts.map +1 -1
- package/dist/mcp-mock.d.ts +33 -2
- package/dist/mcp-mock.d.ts.map +1 -1
- package/dist/mcp-mock.js +112 -1
- package/dist/mcp-mock.js.map +1 -1
- package/dist/mcp-recorder.cjs +1224 -0
- package/dist/mcp-recorder.cjs.map +1 -0
- package/dist/mcp-recorder.d.cts +108 -0
- package/dist/mcp-recorder.d.cts.map +1 -0
- package/dist/mcp-recorder.d.ts +108 -0
- package/dist/mcp-recorder.d.ts.map +1 -0
- package/dist/mcp-recorder.js +1215 -0
- package/dist/mcp-recorder.js.map +1 -0
- package/dist/messages.cjs +362 -56
- package/dist/messages.cjs.map +1 -1
- package/dist/messages.d.cts.map +1 -1
- package/dist/messages.d.ts.map +1 -1
- package/dist/messages.js +363 -58
- package/dist/messages.js.map +1 -1
- package/dist/misbehavior.cjs +1523 -0
- package/dist/misbehavior.cjs.map +1 -0
- package/dist/misbehavior.d.cts +43 -0
- package/dist/misbehavior.d.cts.map +1 -0
- package/dist/misbehavior.d.ts +43 -0
- package/dist/misbehavior.d.ts.map +1 -0
- package/dist/misbehavior.js +1511 -0
- package/dist/misbehavior.js.map +1 -0
- package/dist/ollama.cjs +310 -112
- 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 +311 -113
- package/dist/ollama.js.map +1 -1
- package/dist/openrouter-video.cjs +30 -2
- package/dist/openrouter-video.cjs.map +1 -1
- package/dist/openrouter-video.d.cts.map +1 -1
- package/dist/openrouter-video.d.ts.map +1 -1
- package/dist/openrouter-video.js +30 -2
- package/dist/openrouter-video.js.map +1 -1
- package/dist/record-sanitize.cjs +231 -0
- package/dist/record-sanitize.cjs.map +1 -0
- package/dist/record-sanitize.js +224 -0
- package/dist/record-sanitize.js.map +1 -0
- package/dist/recorder.cjs +106 -5
- package/dist/recorder.cjs.map +1 -1
- package/dist/recorder.d.cts +2 -1
- package/dist/recorder.d.cts.map +1 -1
- package/dist/recorder.d.ts +2 -1
- package/dist/recorder.d.ts.map +1 -1
- package/dist/recorder.js +107 -7
- package/dist/recorder.js.map +1 -1
- package/dist/responses.cjs +667 -171
- package/dist/responses.cjs.map +1 -1
- package/dist/responses.d.cts +5 -13
- package/dist/responses.d.cts.map +1 -1
- package/dist/responses.d.ts +5 -13
- package/dist/responses.d.ts.map +1 -1
- package/dist/responses.js +665 -174
- package/dist/responses.js.map +1 -1
- package/dist/router.cjs +24 -2
- 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 +25 -4
- package/dist/router.js.map +1 -1
- package/dist/server.cjs +572 -76
- 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 +574 -78
- package/dist/server.js.map +1 -1
- package/dist/stream-collapse.cjs +236 -34
- package/dist/stream-collapse.cjs.map +1 -1
- package/dist/stream-collapse.d.cts +39 -4
- package/dist/stream-collapse.d.cts.map +1 -1
- package/dist/stream-collapse.d.ts +39 -4
- package/dist/stream-collapse.d.ts.map +1 -1
- package/dist/stream-collapse.js +235 -35
- package/dist/stream-collapse.js.map +1 -1
- package/dist/types.cjs.map +1 -1
- package/dist/types.d.cts +315 -4
- package/dist/types.d.cts.map +1 -1
- package/dist/types.d.ts +315 -4
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/validate-cli.cjs +36 -4
- package/dist/validate-cli.cjs.map +1 -1
- package/dist/validate-cli.js +37 -5
- package/dist/validate-cli.js.map +1 -1
- package/dist/vector-types.d.ts.map +1 -1
- package/dist/version.cjs +45 -0
- package/dist/version.cjs.map +1 -0
- package/dist/version.js +44 -0
- package/dist/version.js.map +1 -0
- package/dist/vitest.cjs +66 -10
- package/dist/vitest.cjs.map +1 -1
- package/dist/vitest.d.cts +35 -4
- package/dist/vitest.d.cts.map +1 -1
- package/dist/vitest.d.ts +35 -4
- package/dist/vitest.d.ts.map +1 -1
- package/dist/vitest.js +67 -11
- package/dist/vitest.js.map +1 -1
- package/dist/watcher.cjs +6 -2
- package/dist/watcher.cjs.map +1 -1
- package/dist/watcher.js +6 -2
- package/dist/watcher.js.map +1 -1
- package/dist/ws-gemini-live.cjs +210 -101
- package/dist/ws-gemini-live.cjs.map +1 -1
- package/dist/ws-gemini-live.d.cts +9 -3
- package/dist/ws-gemini-live.d.cts.map +1 -1
- package/dist/ws-gemini-live.d.ts +9 -3
- package/dist/ws-gemini-live.d.ts.map +1 -1
- package/dist/ws-gemini-live.js +211 -102
- package/dist/ws-gemini-live.js.map +1 -1
- package/dist/ws-realtime.cjs +175 -62
- package/dist/ws-realtime.cjs.map +1 -1
- package/dist/ws-realtime.d.cts +8 -3
- package/dist/ws-realtime.d.cts.map +1 -1
- package/dist/ws-realtime.d.ts +8 -3
- package/dist/ws-realtime.d.ts.map +1 -1
- package/dist/ws-realtime.js +176 -63
- package/dist/ws-realtime.js.map +1 -1
- package/dist/ws-responses.cjs +109 -16
- package/dist/ws-responses.cjs.map +1 -1
- package/dist/ws-responses.d.cts +8 -12
- package/dist/ws-responses.d.cts.map +1 -1
- package/dist/ws-responses.d.ts +8 -12
- package/dist/ws-responses.d.ts.map +1 -1
- package/dist/ws-responses.js +111 -18
- package/dist/ws-responses.js.map +1 -1
- package/package.json +10 -2
- package/skills/write-fixtures/SKILL.md +179 -21
package/dist/config-loader.d.cts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ApiKeyAuthConfig, ChaosConfig, RecordConfig } from "./types.cjs";
|
|
1
|
+
import { ApiKeyAuthConfig, ChaosConfig, McpRecordConfig, MisbehaviorConfig, MisbehaviorFaultId, RecordConfig, ResponsesToolsMode } from "./types.cjs";
|
|
2
2
|
import { A2AAgentDefinition, A2AArtifact, A2APart, A2AStreamEvent } from "./a2a-types.cjs";
|
|
3
3
|
import { AGUIEvent } from "./agui-types.cjs";
|
|
4
4
|
import { LLMock } from "./llmock.cjs";
|
|
@@ -83,6 +83,11 @@ interface VectorConfig {
|
|
|
83
83
|
path?: string;
|
|
84
84
|
collections?: VectorConfigCollection[];
|
|
85
85
|
}
|
|
86
|
+
/** `llm.record` in a config file: the LLM `RecordConfig`, plus MCP record mounts (C10). */
|
|
87
|
+
interface AimockRecordConfig extends RecordConfig {
|
|
88
|
+
/** MR1: mount path → upstream URL, or a full MCP record config. */
|
|
89
|
+
mcp?: Record<string, string | McpRecordConfig>;
|
|
90
|
+
}
|
|
86
91
|
interface AimockConfig {
|
|
87
92
|
auth?: ApiKeyAuthConfig;
|
|
88
93
|
llm?: {
|
|
@@ -92,7 +97,24 @@ interface AimockConfig {
|
|
|
92
97
|
replaySpeed?: number;
|
|
93
98
|
logLevel?: "silent" | "warn" | "info" | "debug";
|
|
94
99
|
chaos?: ChaosConfig;
|
|
95
|
-
|
|
100
|
+
/**
|
|
101
|
+
* Server-wide misbehavior defaults. Read only when misbehavior is enabled
|
|
102
|
+
* (`llm.enableMisbehavior: true` or `aimock --misbehavior`); otherwise the
|
|
103
|
+
* key is ignored with a warning, as 1.44.0 ignored it.
|
|
104
|
+
*/
|
|
105
|
+
misbehavior?: MisbehaviorConfig | MisbehaviorFaultId;
|
|
106
|
+
/** See `MockServerOptions.enableMisbehavior`. */
|
|
107
|
+
enableMisbehavior?: boolean;
|
|
108
|
+
record?: AimockRecordConfig;
|
|
109
|
+
/**
|
|
110
|
+
* Turns on `llm.record.mcp`. Without it the key is ignored with a warning,
|
|
111
|
+
* as 1.44.0 ignored it.
|
|
112
|
+
*/
|
|
113
|
+
enableMcpRecording?: boolean;
|
|
114
|
+
/** See `MockServerOptions.strictToolArguments`. */
|
|
115
|
+
strictToolArguments?: boolean;
|
|
116
|
+
/** See `MockServerOptions.responsesTools`. */
|
|
117
|
+
responsesTools?: ResponsesToolsMode;
|
|
96
118
|
};
|
|
97
119
|
mcp?: MCPConfig;
|
|
98
120
|
a2a?: A2AConfig;
|
|
@@ -109,10 +131,21 @@ interface AimockConfig {
|
|
|
109
131
|
host?: string;
|
|
110
132
|
}
|
|
111
133
|
declare function loadConfig(configPath: string): AimockConfig;
|
|
112
|
-
|
|
134
|
+
/**
|
|
135
|
+
* Command-line overrides for `startFromConfig`. The opt-in flags win over the
|
|
136
|
+
* matching `llm.*` config keys.
|
|
137
|
+
*/
|
|
138
|
+
interface StartFromConfigOverrides {
|
|
113
139
|
port?: number;
|
|
114
140
|
host?: string;
|
|
115
|
-
|
|
141
|
+
/** `aimock --misbehavior`: overrides `llm.enableMisbehavior`. */
|
|
142
|
+
enableMisbehavior?: boolean;
|
|
143
|
+
/** `aimock --strict-tool-arguments`: overrides `llm.strictToolArguments`. */
|
|
144
|
+
strictToolArguments?: boolean;
|
|
145
|
+
/** `aimock --responses-tools`: overrides `llm.responsesTools`. */
|
|
146
|
+
responsesTools?: ResponsesToolsMode;
|
|
147
|
+
}
|
|
148
|
+
declare function startFromConfig(config: AimockConfig, overrides?: StartFromConfigOverrides): Promise<{
|
|
116
149
|
llmock: LLMock;
|
|
117
150
|
url: string;
|
|
118
151
|
}>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config-loader.d.cts","names":[],"sources":["../src/config-loader.ts"],"sourcesContent":[],"mappings":";;;;;;;;
|
|
1
|
+
{"version":3,"file":"config-loader.d.cts","names":[],"sources":["../src/config-loader.ts"],"sourcesContent":[],"mappings":";;;;;;;;UA4BiB,aAAA,SAAsB;;AAAvC;AAIiB,UAAA,iBAAA,CAAiB;EASjB,GAAA,EAAA,MAAA;EAAgB,IAAA,EAAA,MAAA;UAEnB,CAAA,EAAA,MAAA;aAF2B,CAAA,EAAA,MAAA;EAAmB,IAAA,CAAA,EAAA,MAAA;EAM3C,IAAA,CAAA,EAAA,MAAS;;AAGhB,UATO,eAAA,SAAwB,mBAS/B,CAAA;QACI,CAAA,EAAA;IACF,QAAA,EATE,KASF,CAAA;MAAe,IAAA,EAAA,MAAA;MAGV,OAAA,EAAA;QAAgB,IAAA,EAAA,MAAA;QAEvB,IAAA,EAAA,MAAA;MACI,CAAA;IACH,CAAA,CAAA;EAAc,CAAA;AAIzB;AAAgC,UAhBf,SAAA,CAgBe;MACnB,CAAA,EAAA,MAAA;YACH,CAAA,EAAA;IACS,IAAA,EAAA,MAAA;IAHqB,OAAA,EAAA,MAAA;EAAkB,CAAA;EAMzC,KAAA,CAAA,EAnBP,aAmBgB,EAEf;EAGM,SAAA,CAAA,EAvBH,iBAuBoB,EAGvB;EAIM,OAAA,CAAA,EA7BL,eA+BC,EAAA;AAGb;AAAuC,UA/BtB,gBAAA,CA+BsB;SAMxB,EAAA,MAAA;OAHH,CAAA,EAhCF,OAgCE,EAAA;WAKK,CAAA,EApCH,WAoCG,EAAA;EAAW,MAAA,CAAA,EAnCjB,cAmCiB,EAAA;EAGX,OAAA,CAAA,EAAA,MAAY;AAM7B;AAAoC,UAxCnB,cAAA,SAAuB,kBAwCJ,CAAA;UAEJ,CAAA,EAzCnB,gBAyCmB,EAAA;OAAxB,CAAA,EAxCE,gBAwCF,EAAA;gBAFoC,CAAA,EArCzB,gBAqCyB,EAAA;;AAK3B,UAvCA,SAAA,CAuCY;EAAA,IAAA,CAAA,EAAA,MAAA;QACpB,CAAA,EAtCE,cAsCF,EAAA;;AAaS,UAhDD,iBAAA,CAgDC;OAAoB,EAAA;IAGzB,OAAA,CAAA,EAAA,MAAA;IASQ,UAAA,CAAA,EAAA,MAAA;IAEb,QAAA,CAAA,EAAA,MAAA;IACA,QAAA,CAAA,EAAA,MAAA;;MAEG,CAAA,EAAA,MAAA;EAAY,MAAA,CAAA,EA9DZ,SA8DY,EAAA;EAgBP,OAAA,CAAA,EAAA,MAAU;AAgB1B;AAWsB,UArGL,UAAA,CAqGoB;EAAA,IAAA,CAAA,EAAA,MAAA;UAC3B,CAAA,EApGG,iBAoGH,EAAA;;AAEW,UAnGJ,sBAAA,CAmGI;MAAlB,EAAA,MAAA;EAAO,SAAA,EAAA,MAAA;YAhGE;;;eAGG;;iBAEE;;UAGA,YAAA;;gBAED;;;UAIC,kBAAA,SAA2B;;QAEpC,wBAAwB;;UAGf,YAAA;SACR;;;;;;;YAOG;;;;;;kBAMM,oBAAoB;;;aAGzB;;;;;;;;;qBASQ;;QAEb;QACA;SACC;WACE;;;;;;;;;;;iBAgBK,UAAA,sBAAgC;;;;;UAgB/B,wBAAA;;;;;;;;mBAQE;;iBAGG,eAAA,SACZ,0BACI,2BACX;UAAkB"}
|
package/dist/config-loader.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ApiKeyAuthConfig, ChaosConfig, RecordConfig } from "./types.js";
|
|
1
|
+
import { ApiKeyAuthConfig, ChaosConfig, McpRecordConfig, MisbehaviorConfig, MisbehaviorFaultId, RecordConfig, ResponsesToolsMode } from "./types.js";
|
|
2
2
|
import { A2AAgentDefinition, A2AArtifact, A2APart, A2AStreamEvent } from "./a2a-types.js";
|
|
3
3
|
import { AGUIEvent } from "./agui-types.js";
|
|
4
4
|
import { LLMock } from "./llmock.js";
|
|
@@ -83,6 +83,11 @@ interface VectorConfig {
|
|
|
83
83
|
path?: string;
|
|
84
84
|
collections?: VectorConfigCollection[];
|
|
85
85
|
}
|
|
86
|
+
/** `llm.record` in a config file: the LLM `RecordConfig`, plus MCP record mounts (C10). */
|
|
87
|
+
interface AimockRecordConfig extends RecordConfig {
|
|
88
|
+
/** MR1: mount path → upstream URL, or a full MCP record config. */
|
|
89
|
+
mcp?: Record<string, string | McpRecordConfig>;
|
|
90
|
+
}
|
|
86
91
|
interface AimockConfig {
|
|
87
92
|
auth?: ApiKeyAuthConfig;
|
|
88
93
|
llm?: {
|
|
@@ -92,7 +97,24 @@ interface AimockConfig {
|
|
|
92
97
|
replaySpeed?: number;
|
|
93
98
|
logLevel?: "silent" | "warn" | "info" | "debug";
|
|
94
99
|
chaos?: ChaosConfig;
|
|
95
|
-
|
|
100
|
+
/**
|
|
101
|
+
* Server-wide misbehavior defaults. Read only when misbehavior is enabled
|
|
102
|
+
* (`llm.enableMisbehavior: true` or `aimock --misbehavior`); otherwise the
|
|
103
|
+
* key is ignored with a warning, as 1.44.0 ignored it.
|
|
104
|
+
*/
|
|
105
|
+
misbehavior?: MisbehaviorConfig | MisbehaviorFaultId;
|
|
106
|
+
/** See `MockServerOptions.enableMisbehavior`. */
|
|
107
|
+
enableMisbehavior?: boolean;
|
|
108
|
+
record?: AimockRecordConfig;
|
|
109
|
+
/**
|
|
110
|
+
* Turns on `llm.record.mcp`. Without it the key is ignored with a warning,
|
|
111
|
+
* as 1.44.0 ignored it.
|
|
112
|
+
*/
|
|
113
|
+
enableMcpRecording?: boolean;
|
|
114
|
+
/** See `MockServerOptions.strictToolArguments`. */
|
|
115
|
+
strictToolArguments?: boolean;
|
|
116
|
+
/** See `MockServerOptions.responsesTools`. */
|
|
117
|
+
responsesTools?: ResponsesToolsMode;
|
|
96
118
|
};
|
|
97
119
|
mcp?: MCPConfig;
|
|
98
120
|
a2a?: A2AConfig;
|
|
@@ -109,10 +131,21 @@ interface AimockConfig {
|
|
|
109
131
|
host?: string;
|
|
110
132
|
}
|
|
111
133
|
declare function loadConfig(configPath: string): AimockConfig;
|
|
112
|
-
|
|
134
|
+
/**
|
|
135
|
+
* Command-line overrides for `startFromConfig`. The opt-in flags win over the
|
|
136
|
+
* matching `llm.*` config keys.
|
|
137
|
+
*/
|
|
138
|
+
interface StartFromConfigOverrides {
|
|
113
139
|
port?: number;
|
|
114
140
|
host?: string;
|
|
115
|
-
|
|
141
|
+
/** `aimock --misbehavior`: overrides `llm.enableMisbehavior`. */
|
|
142
|
+
enableMisbehavior?: boolean;
|
|
143
|
+
/** `aimock --strict-tool-arguments`: overrides `llm.strictToolArguments`. */
|
|
144
|
+
strictToolArguments?: boolean;
|
|
145
|
+
/** `aimock --responses-tools`: overrides `llm.responsesTools`. */
|
|
146
|
+
responsesTools?: ResponsesToolsMode;
|
|
147
|
+
}
|
|
148
|
+
declare function startFromConfig(config: AimockConfig, overrides?: StartFromConfigOverrides): Promise<{
|
|
116
149
|
llmock: LLMock;
|
|
117
150
|
url: string;
|
|
118
151
|
}>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config-loader.d.ts","names":[],"sources":["../src/config-loader.ts"],"sourcesContent":[],"mappings":";;;;;;;;
|
|
1
|
+
{"version":3,"file":"config-loader.d.ts","names":[],"sources":["../src/config-loader.ts"],"sourcesContent":[],"mappings":";;;;;;;;UA4BiB,aAAA,SAAsB;;AAAvC;AAIiB,UAAA,iBAAA,CAAiB;EASjB,GAAA,EAAA,MAAA;EAAgB,IAAA,EAAA,MAAA;UAEnB,CAAA,EAAA,MAAA;aAF2B,CAAA,EAAA,MAAA;EAAmB,IAAA,CAAA,EAAA,MAAA;EAM3C,IAAA,CAAA,EAAA,MAAS;;AAGhB,UATO,eAAA,SAAwB,mBAS/B,CAAA;QACI,CAAA,EAAA;IACF,QAAA,EATE,KASF,CAAA;MAAe,IAAA,EAAA,MAAA;MAGV,OAAA,EAAA;QAAgB,IAAA,EAAA,MAAA;QAEvB,IAAA,EAAA,MAAA;MACI,CAAA;IACH,CAAA,CAAA;EAAc,CAAA;AAIzB;AAAgC,UAhBf,SAAA,CAgBe;MACnB,CAAA,EAAA,MAAA;YACH,CAAA,EAAA;IACS,IAAA,EAAA,MAAA;IAHqB,OAAA,EAAA,MAAA;EAAkB,CAAA;EAMzC,KAAA,CAAA,EAnBP,aAmBgB,EAAA;EAKT,SAAA,CAAA,EAvBH,iBAuBoB,EAAA;EAOjB,OAAA,CAAA,EA7BL,eA+BC,EAAA;AAGb;AAAuC,UA/BtB,gBAAA,CA+BsB;SAMxB,EAAA,MAAA;OAHH,CAAA,EAhCF,OAgCE,EAAA;WAKK,CAAA,EApCH,WAoCG,EAAA;EAAW,MAAA,CAAA,EAnCjB,cAmCiB,EAAA;EAGX,OAAA,CAAA,EAAA,MAAY;AAM7B;AAAoC,UAxCnB,cAAA,SAAuB,kBAwCJ,CAAA;UAEJ,CAAA,EAzCnB,gBAyCmB,EAAA;OAAxB,CAAA,EAxCE,gBAwCF,EAAA;gBAFoC,CAAA,EArCzB,gBAqCyB,EAAA;;AAK3B,UAvCA,SAAA,CAuCY;EAAA,IAAA,CAAA,EAAA,MAAA;QACpB,CAAA,EAtCE,cAsCF,EAAA;;AAaS,UAhDD,iBAAA,CAgDC;OAAoB,EAAA;IAGzB,OAAA,CAAA,EAAA,MAAA;IASQ,UAAA,CAAA,EAAA,MAAA;IAEb,QAAA,CAAA,EAAA,MAAA;IACA,QAAA,CAAA,EAAA,MAAA;;MAEG,CAAA,EAAA,MAAA;EAAY,MAAA,CAAA,EA9DZ,SA8DY,EAAA;EAgBP,OAAA,CAAA,EAAA,MAAU;AAgB1B;AAWsB,UArGL,UAAA,CAqGoB;EAAA,IAAA,CAAA,EAAA,MAAA;UAC3B,CAAA,EApGG,iBAoGH,EAAA;;AAEW,UAnGJ,sBAAA,CAmGI;MAAlB,EAAA,MAAA;EAAO,SAAA,EAAA,MAAA;YAhGE;;;eAGG;;iBAEE;;UAGA,YAAA;;gBAED;;;UAIC,kBAAA,SAA2B;;QAEpC,wBAAwB;;UAGf,YAAA;SACR;;;;;;;YAOG;;;;;;kBAMM,oBAAoB;;;aAGzB;;;;;;;;;qBASQ;;QAEb;QACA;SACC;WACE;;;;;;;;;;;iBAgBK,UAAA,sBAAgC;;;;;UAgB/B,wBAAA;;;;;;;;mBAQE;;iBAGG,eAAA,SACZ,0BACI,2BACX;UAAkB"}
|
package/dist/config-loader.js
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import { resolveInboundAuth, selectInboundAuthSource } from "./api-key-auth.js";
|
|
2
2
|
import { A2AMock } from "./a2a-mock.js";
|
|
3
3
|
import { buildTextResponse } from "./agui-handler.js";
|
|
4
|
+
import { parseMisbehavior } from "./misbehavior.js";
|
|
4
5
|
import { Logger } from "./logger.js";
|
|
5
6
|
import { AGUIMock } from "./agui-mock.js";
|
|
7
|
+
import { configToolHandlers } from "./mcp-handler.js";
|
|
8
|
+
import { wireMcpRecording } from "./mcp-recorder.js";
|
|
6
9
|
import { MCPMock } from "./mcp-mock.js";
|
|
7
10
|
import { createLLMockWithResolvedAuth } from "./llmock.js";
|
|
8
11
|
import { VectorMock } from "./vector-mock.js";
|
|
@@ -10,12 +13,46 @@ import * as fs from "node:fs";
|
|
|
10
13
|
import * as path from "node:path";
|
|
11
14
|
|
|
12
15
|
//#region src/config-loader.ts
|
|
16
|
+
/** C10: the LLM part of `llm.record`. `mcp` is MCP-only; no `providers` means no LLM recording. */
|
|
17
|
+
function llmRecordOf(record) {
|
|
18
|
+
if (!record) return void 0;
|
|
19
|
+
const { mcp: _mcp, ...llm } = record;
|
|
20
|
+
return llm.providers === void 0 ? void 0 : llm;
|
|
21
|
+
}
|
|
13
22
|
function loadConfig(configPath) {
|
|
14
23
|
const raw = fs.readFileSync(configPath, "utf-8");
|
|
15
24
|
return JSON.parse(raw);
|
|
16
25
|
}
|
|
26
|
+
/** C3: `llm.record.mcp` is an object with at least one mount. */
|
|
27
|
+
function hasMcpRecordMount(mcp) {
|
|
28
|
+
return typeof mcp === "object" && mcp !== null && !Array.isArray(mcp) && Object.keys(mcp).length > 0;
|
|
29
|
+
}
|
|
17
30
|
async function startFromConfig(config, overrides) {
|
|
18
31
|
const logger = new Logger("info");
|
|
32
|
+
const booleanOptIn = (key) => {
|
|
33
|
+
const value = config.llm?.[key];
|
|
34
|
+
if (value === void 0 || typeof value === "boolean") return value;
|
|
35
|
+
logger.warn(`Ignoring llm.${key} because it must be true or false, got ${JSON.stringify(value)}.`);
|
|
36
|
+
};
|
|
37
|
+
const configEnableMisbehavior = booleanOptIn("enableMisbehavior");
|
|
38
|
+
const configStrictToolArguments = booleanOptIn("strictToolArguments");
|
|
39
|
+
const enableMcpRecording = booleanOptIn("enableMcpRecording") === true;
|
|
40
|
+
const mcpRecord = enableMcpRecording ? config.llm?.record?.mcp : void 0;
|
|
41
|
+
const enableMisbehavior = (overrides?.enableMisbehavior ?? configEnableMisbehavior) === true;
|
|
42
|
+
let misbehavior;
|
|
43
|
+
if (config.llm?.misbehavior !== void 0) if (!enableMisbehavior) logger.warn("Ignoring llm.misbehavior because misbehavior is not enabled. Set llm.enableMisbehavior: true or pass --misbehavior to use it.");
|
|
44
|
+
else {
|
|
45
|
+
const parsed = parseMisbehavior(config.llm.misbehavior, "llm.misbehavior");
|
|
46
|
+
if (!parsed.ok) throw new TypeError(`${parsed.issue.rule}: ${parsed.issue.path}: ${parsed.issue.message}`);
|
|
47
|
+
misbehavior = parsed.config;
|
|
48
|
+
}
|
|
49
|
+
if (config.llm?.record?.mcp !== void 0 && !enableMcpRecording) logger.warn("Ignoring llm.record.mcp because MCP recording is not enabled. Set llm.enableMcpRecording: true to use it.");
|
|
50
|
+
let responsesTools = config.llm?.responsesTools;
|
|
51
|
+
if (responsesTools !== void 0 && responsesTools !== "legacy" && responsesTools !== "extended") {
|
|
52
|
+
const effective = overrides?.responsesTools !== void 0 ? `"${overrides.responsesTools}" from --responses-tools` : "\"legacy\"";
|
|
53
|
+
logger.warn(`Ignoring llm.responsesTools because it must be "legacy" or "extended", got ${JSON.stringify(responsesTools)}. Using ${effective}.`);
|
|
54
|
+
responsesTools = void 0;
|
|
55
|
+
}
|
|
19
56
|
let replaySpeed = config.llm?.replaySpeed;
|
|
20
57
|
if (replaySpeed != null && (!Number.isFinite(replaySpeed) || replaySpeed <= 0)) {
|
|
21
58
|
logger.warn(`llm.replaySpeed must be a positive number, got ${replaySpeed}. Ignoring.`);
|
|
@@ -28,12 +65,17 @@ async function startFromConfig(config, overrides) {
|
|
|
28
65
|
latency: config.llm?.latency,
|
|
29
66
|
chunkSize: config.llm?.chunkSize,
|
|
30
67
|
replaySpeed,
|
|
31
|
-
logLevel: config.llm?.logLevel,
|
|
68
|
+
logLevel: config.llm?.logLevel ?? (hasMcpRecordMount(mcpRecord) ? "warn" : void 0),
|
|
32
69
|
chaos: config.llm?.chaos,
|
|
33
|
-
|
|
70
|
+
misbehavior,
|
|
71
|
+
...enableMisbehavior ? { enableMisbehavior: true } : {},
|
|
72
|
+
record: llmRecordOf(config.llm?.record),
|
|
34
73
|
metrics: config.metrics,
|
|
35
|
-
strict: config.strict
|
|
74
|
+
strict: config.strict,
|
|
75
|
+
strictToolArguments: overrides?.strictToolArguments ?? configStrictToolArguments,
|
|
76
|
+
responsesTools: overrides?.responsesTools ?? responsesTools
|
|
36
77
|
}, resolvedAuth);
|
|
78
|
+
const configMounts = /* @__PURE__ */ new Map();
|
|
37
79
|
if (config.llm?.fixtures) {
|
|
38
80
|
const fixturePath = path.resolve(config.llm.fixtures);
|
|
39
81
|
if (fs.statSync(fixturePath).isDirectory()) llmock.loadFixtureDir(fixturePath);
|
|
@@ -45,7 +87,11 @@ async function startFromConfig(config, overrides) {
|
|
|
45
87
|
if (mcpConfig.tools) for (const tool of mcpConfig.tools) {
|
|
46
88
|
const { result, ...def } = tool;
|
|
47
89
|
mcp.addTool(def);
|
|
48
|
-
if (result !== void 0)
|
|
90
|
+
if (result !== void 0) {
|
|
91
|
+
const answer = () => result;
|
|
92
|
+
configToolHandlers.add(answer);
|
|
93
|
+
mcp.onToolCall(def.name, answer);
|
|
94
|
+
}
|
|
49
95
|
}
|
|
50
96
|
if (mcpConfig.resources) for (const res of mcpConfig.resources) mcp.addResource({
|
|
51
97
|
uri: res.uri,
|
|
@@ -64,6 +110,7 @@ async function startFromConfig(config, overrides) {
|
|
|
64
110
|
}
|
|
65
111
|
const mcpPath = mcpConfig.path ?? "/mcp";
|
|
66
112
|
llmock.mount(mcpPath, mcp);
|
|
113
|
+
configMounts.set(mcpPath, mcp);
|
|
67
114
|
logger.info(`MCPMock mounted at ${mcpPath}`);
|
|
68
115
|
}
|
|
69
116
|
if (config.a2a) {
|
|
@@ -78,6 +125,7 @@ async function startFromConfig(config, overrides) {
|
|
|
78
125
|
}
|
|
79
126
|
const a2aPath = a2aConfig.path ?? "/a2a";
|
|
80
127
|
llmock.mount(a2aPath, a2a);
|
|
128
|
+
configMounts.set(a2aPath, a2a);
|
|
81
129
|
logger.info(`A2AMock mounted at ${a2aPath}`);
|
|
82
130
|
}
|
|
83
131
|
if (config.agui) {
|
|
@@ -109,6 +157,7 @@ async function startFromConfig(config, overrides) {
|
|
|
109
157
|
}
|
|
110
158
|
const aguiPath = aguiConfig.path ?? "/agui";
|
|
111
159
|
llmock.mount(aguiPath, agui);
|
|
160
|
+
configMounts.set(aguiPath, agui);
|
|
112
161
|
logger.info(`AGUIMock mounted at ${aguiPath}`);
|
|
113
162
|
}
|
|
114
163
|
if (config.vector) {
|
|
@@ -121,8 +170,17 @@ async function startFromConfig(config, overrides) {
|
|
|
121
170
|
}
|
|
122
171
|
const vectorPath = vectorConfig.path ?? "/vector";
|
|
123
172
|
llmock.mount(vectorPath, vector);
|
|
173
|
+
configMounts.set(vectorPath, vector);
|
|
124
174
|
logger.info(`VectorMock mounted at ${vectorPath}`);
|
|
125
175
|
}
|
|
176
|
+
if (mcpRecord !== void 0) wireMcpRecording(llmock, mcpRecord, {
|
|
177
|
+
mounted: configMounts,
|
|
178
|
+
mcpMock: MCPMock,
|
|
179
|
+
fixtures: config.llm?.fixtures,
|
|
180
|
+
fixturePath: config.llm?.record?.fixturePath,
|
|
181
|
+
strict: config.strict,
|
|
182
|
+
env: process.env
|
|
183
|
+
});
|
|
126
184
|
if (config.services) {
|
|
127
185
|
if (config.services.search) {
|
|
128
186
|
llmock.onSearch(/.*/, []);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config-loader.js","names":[],"sources":["../src/config-loader.ts"],"sourcesContent":["import * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport { LLMock, createLLMockWithResolvedAuth } from \"./llmock.js\";\nimport { resolveInboundAuth, selectInboundAuthSource } from \"./api-key-auth.js\";\nimport { MCPMock } from \"./mcp-mock.js\";\nimport { A2AMock } from \"./a2a-mock.js\";\nimport { AGUIMock } from \"./agui-mock.js\";\nimport type { ApiKeyAuthConfig, ChaosConfig, RecordConfig } from \"./types.js\";\nimport type { MCPToolDefinition, MCPPromptDefinition } from \"./mcp-types.js\";\nimport type { A2AAgentDefinition, A2APart, A2AArtifact, A2AStreamEvent } from \"./a2a-types.js\";\nimport type { AGUIEvent } from \"./agui-types.js\";\nimport { buildTextResponse } from \"./agui-handler.js\";\nimport { VectorMock } from \"./vector-mock.js\";\nimport type { QueryResult } from \"./vector-types.js\";\nimport { Logger } from \"./logger.js\";\n\nexport interface MCPConfigTool extends MCPToolDefinition {\n result?: string;\n}\n\nexport interface MCPConfigResource {\n uri: string;\n name: string;\n mimeType?: string;\n description?: string;\n text?: string;\n blob?: string;\n}\n\nexport interface MCPConfigPrompt extends MCPPromptDefinition {\n result?: {\n messages: Array<{ role: string; content: { type: string; text: string } }>;\n };\n}\n\nexport interface MCPConfig {\n path?: string;\n serverInfo?: { name: string; version: string };\n tools?: MCPConfigTool[];\n resources?: MCPConfigResource[];\n prompts?: MCPConfigPrompt[];\n}\n\nexport interface A2AConfigPattern {\n pattern: string;\n parts?: A2APart[];\n artifacts?: A2AArtifact[];\n events?: A2AStreamEvent[];\n delayMs?: number;\n}\n\nexport interface A2AConfigAgent extends A2AAgentDefinition {\n messages?: A2AConfigPattern[];\n tasks?: A2AConfigPattern[];\n streamingTasks?: A2AConfigPattern[];\n}\n\nexport interface A2AConfig {\n path?: string;\n agents?: A2AConfigAgent[];\n}\n\nexport interface AGUIConfigFixture {\n match: { message?: string; toolCallId?: string; toolName?: string; stateKey?: string };\n text?: string; // shorthand: uses buildTextResponse\n events?: AGUIEvent[]; // raw events\n delayMs?: number;\n}\n\nexport interface AGUIConfig {\n path?: string; // mount path, default \"/agui\"\n fixtures?: AGUIConfigFixture[];\n}\n\nexport interface VectorConfigCollection {\n name: string;\n dimension: number;\n vectors?: Array<{\n id: string;\n values: number[];\n metadata?: Record<string, unknown>;\n }>;\n queryResults?: QueryResult[];\n}\n\nexport interface VectorConfig {\n path?: string;\n collections?: VectorConfigCollection[];\n}\n\nexport interface AimockConfig {\n auth?: ApiKeyAuthConfig;\n llm?: {\n fixtures?: string;\n latency?: number;\n chunkSize?: number;\n replaySpeed?: number;\n logLevel?: \"silent\" | \"warn\" | \"info\" | \"debug\";\n chaos?: ChaosConfig;\n record?: RecordConfig;\n };\n mcp?: MCPConfig;\n a2a?: A2AConfig;\n agui?: AGUIConfig;\n vector?: VectorConfig;\n services?: { search?: boolean; rerank?: boolean; moderate?: boolean };\n metrics?: boolean;\n strict?: boolean;\n port?: number;\n host?: string;\n}\n\nexport function loadConfig(configPath: string): AimockConfig {\n const raw = fs.readFileSync(configPath, \"utf-8\");\n return JSON.parse(raw) as AimockConfig;\n}\n\nexport async function startFromConfig(\n config: AimockConfig,\n overrides?: { port?: number; host?: string },\n): Promise<{ llmock: LLMock; url: string }> {\n const logger = new Logger(\"info\");\n\n // A non-positive replaySpeed fails calculateDelay's `speed > 0` check and applies the\n // full delay rather than none. Mirrors the fixture-level guard in fixture-loader.ts.\n let replaySpeed = config.llm?.replaySpeed;\n if (replaySpeed != null && (!Number.isFinite(replaySpeed) || replaySpeed <= 0)) {\n logger.warn(`llm.replaySpeed must be a positive number, got ${replaySpeed}. Ignoring.`);\n replaySpeed = undefined;\n }\n\n // Load fixtures if specified\n const resolvedAuth = resolveInboundAuth(selectInboundAuthSource(config.auth));\n const llmock = createLLMockWithResolvedAuth(\n {\n port: overrides?.port ?? config.port ?? 0,\n host: overrides?.host ?? config.host ?? \"127.0.0.1\",\n latency: config.llm?.latency,\n chunkSize: config.llm?.chunkSize,\n replaySpeed,\n logLevel: config.llm?.logLevel,\n chaos: config.llm?.chaos,\n record: config.llm?.record,\n metrics: config.metrics,\n strict: config.strict,\n },\n resolvedAuth,\n );\n\n if (config.llm?.fixtures) {\n const fixturePath = path.resolve(config.llm.fixtures);\n const stat = fs.statSync(fixturePath);\n if (stat.isDirectory()) {\n llmock.loadFixtureDir(fixturePath);\n } else {\n llmock.loadFixtureFile(fixturePath);\n }\n }\n\n // MCP\n if (config.mcp) {\n const mcpConfig = config.mcp;\n const mcp = new MCPMock({\n serverInfo: mcpConfig.serverInfo,\n });\n\n if (mcpConfig.tools) {\n for (const tool of mcpConfig.tools) {\n const { result, ...def } = tool;\n mcp.addTool(def);\n if (result !== undefined) {\n mcp.onToolCall(def.name, () => result);\n }\n }\n }\n\n if (mcpConfig.resources) {\n for (const res of mcpConfig.resources) {\n mcp.addResource(\n { uri: res.uri, name: res.name, mimeType: res.mimeType, description: res.description },\n res.text !== undefined || res.blob !== undefined\n ? { text: res.text, blob: res.blob, mimeType: res.mimeType }\n : undefined,\n );\n }\n }\n\n if (mcpConfig.prompts) {\n for (const prompt of mcpConfig.prompts) {\n const { result, ...def } = prompt;\n if (result) {\n mcp.addPrompt(def, () => result as import(\"./mcp-types.js\").MCPPromptResult);\n } else {\n mcp.addPrompt(def);\n }\n }\n }\n\n const mcpPath = mcpConfig.path ?? \"/mcp\";\n llmock.mount(mcpPath, mcp);\n logger.info(`MCPMock mounted at ${mcpPath}`);\n }\n\n // A2A\n if (config.a2a) {\n const a2aConfig = config.a2a;\n const a2a = new A2AMock();\n\n if (a2aConfig.agents) {\n for (const agentConfig of a2aConfig.agents) {\n const { messages, tasks, streamingTasks, ...def } = agentConfig;\n a2a.registerAgent(def);\n\n if (messages) {\n for (const m of messages) {\n a2a.onMessage(def.name, m.pattern, m.parts ?? [{ text: \"\" }]);\n }\n }\n\n if (tasks) {\n for (const t of tasks) {\n a2a.onTask(def.name, t.pattern, t.artifacts ?? []);\n }\n }\n\n if (streamingTasks) {\n for (const s of streamingTasks) {\n a2a.onStreamingTask(def.name, s.pattern, s.events ?? [], s.delayMs);\n }\n }\n }\n }\n\n const a2aPath = a2aConfig.path ?? \"/a2a\";\n llmock.mount(a2aPath, a2a);\n logger.info(`A2AMock mounted at ${a2aPath}`);\n }\n\n // AG-UI\n if (config.agui) {\n const aguiConfig = config.agui;\n const agui = new AGUIMock();\n\n if (aguiConfig.fixtures) {\n for (const f of aguiConfig.fixtures) {\n if (f.match.toolCallId && f.text && !f.events) {\n logger.warn(\n `AG-UI fixture uses text shorthand with toolCallId — text shorthand ignores toolCallId matching; use events[] instead (match: ${JSON.stringify(f.match)})`,\n );\n }\n if (f.text) {\n if (f.match.message !== undefined) {\n agui.onMessage(f.match.message, f.text, { delayMs: f.delayMs });\n } else {\n // No message pattern — register via addFixture so it only matches\n // on other criteria (toolCallId, toolName, stateKey) instead of\n // becoming a catch-all that matches every request.\n agui.addFixture({\n match: {\n toolCallId: f.match.toolCallId,\n toolName: f.match.toolName,\n stateKey: f.match.stateKey,\n },\n events: buildTextResponse(f.text),\n delayMs: f.delayMs,\n });\n }\n } else if (f.events) {\n agui.addFixture({\n match: {\n message: f.match.message,\n toolCallId: f.match.toolCallId,\n toolName: f.match.toolName,\n stateKey: f.match.stateKey,\n },\n events: f.events,\n delayMs: f.delayMs,\n });\n } else {\n logger.warn(\n `AG-UI fixture has neither text nor events — it will be skipped (match: ${JSON.stringify(f.match)})`,\n );\n }\n }\n }\n\n const aguiPath = aguiConfig.path ?? \"/agui\";\n llmock.mount(aguiPath, agui);\n logger.info(`AGUIMock mounted at ${aguiPath}`);\n }\n\n // Vector\n if (config.vector) {\n const vectorConfig = config.vector;\n const vector = new VectorMock();\n\n if (vectorConfig.collections) {\n for (const col of vectorConfig.collections) {\n vector.addCollection(col.name, { dimension: col.dimension });\n\n if (col.vectors && col.vectors.length > 0) {\n vector.upsert(col.name, col.vectors);\n }\n\n if (col.queryResults) {\n vector.onQuery(col.name, col.queryResults);\n }\n }\n }\n\n const vectorPath = vectorConfig.path ?? \"/vector\";\n llmock.mount(vectorPath, vector);\n logger.info(`VectorMock mounted at ${vectorPath}`);\n }\n\n // Services — configure default catch-all responses\n if (config.services) {\n if (config.services.search) {\n llmock.onSearch(/.*/, []);\n logger.info(\"Search service enabled with default empty results\");\n }\n if (config.services.rerank) {\n llmock.onRerank(/.*/, []);\n logger.info(\"Rerank service enabled with default empty results\");\n }\n if (config.services.moderate) {\n llmock.onModerate(/.*/, { flagged: false, categories: {} });\n logger.info(\"Moderation service enabled with default unflagged results\");\n }\n }\n\n const url = await llmock.start();\n return { llmock, url };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"config-loader.js","names":[],"sources":["../src/config-loader.ts"],"sourcesContent":["import * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport { LLMock, createLLMockWithResolvedAuth } from \"./llmock.js\";\nimport { resolveInboundAuth, selectInboundAuthSource } from \"./api-key-auth.js\";\nimport { MCPMock } from \"./mcp-mock.js\";\nimport { configToolHandlers } from \"./mcp-handler.js\";\nimport { A2AMock } from \"./a2a-mock.js\";\nimport { AGUIMock } from \"./agui-mock.js\";\nimport type {\n ApiKeyAuthConfig,\n ChaosConfig,\n McpRecordConfig,\n MisbehaviorConfig,\n MisbehaviorFaultId,\n Mountable,\n RecordConfig,\n ResponsesToolsMode,\n} from \"./types.js\";\nimport { parseMisbehavior } from \"./misbehavior.js\";\nimport type { MCPToolDefinition, MCPPromptDefinition } from \"./mcp-types.js\";\nimport type { A2AAgentDefinition, A2APart, A2AArtifact, A2AStreamEvent } from \"./a2a-types.js\";\nimport type { AGUIEvent } from \"./agui-types.js\";\nimport { buildTextResponse } from \"./agui-handler.js\";\nimport { VectorMock } from \"./vector-mock.js\";\nimport type { QueryResult } from \"./vector-types.js\";\nimport { Logger } from \"./logger.js\";\nimport { wireMcpRecording } from \"./mcp-recorder.js\";\n\nexport interface MCPConfigTool extends MCPToolDefinition {\n result?: string;\n}\n\nexport interface MCPConfigResource {\n uri: string;\n name: string;\n mimeType?: string;\n description?: string;\n text?: string;\n blob?: string;\n}\n\nexport interface MCPConfigPrompt extends MCPPromptDefinition {\n result?: {\n messages: Array<{ role: string; content: { type: string; text: string } }>;\n };\n}\n\nexport interface MCPConfig {\n path?: string;\n serverInfo?: { name: string; version: string };\n tools?: MCPConfigTool[];\n resources?: MCPConfigResource[];\n prompts?: MCPConfigPrompt[];\n}\n\nexport interface A2AConfigPattern {\n pattern: string;\n parts?: A2APart[];\n artifacts?: A2AArtifact[];\n events?: A2AStreamEvent[];\n delayMs?: number;\n}\n\nexport interface A2AConfigAgent extends A2AAgentDefinition {\n messages?: A2AConfigPattern[];\n tasks?: A2AConfigPattern[];\n streamingTasks?: A2AConfigPattern[];\n}\n\nexport interface A2AConfig {\n path?: string;\n agents?: A2AConfigAgent[];\n}\n\nexport interface AGUIConfigFixture {\n match: { message?: string; toolCallId?: string; toolName?: string; stateKey?: string };\n text?: string; // shorthand: uses buildTextResponse\n events?: AGUIEvent[]; // raw events\n delayMs?: number;\n}\n\nexport interface AGUIConfig {\n path?: string; // mount path, default \"/agui\"\n fixtures?: AGUIConfigFixture[];\n}\n\nexport interface VectorConfigCollection {\n name: string;\n dimension: number;\n vectors?: Array<{\n id: string;\n values: number[];\n metadata?: Record<string, unknown>;\n }>;\n queryResults?: QueryResult[];\n}\n\nexport interface VectorConfig {\n path?: string;\n collections?: VectorConfigCollection[];\n}\n\n/** `llm.record` in a config file: the LLM `RecordConfig`, plus MCP record mounts (C10). */\nexport interface AimockRecordConfig extends RecordConfig {\n /** MR1: mount path → upstream URL, or a full MCP record config. */\n mcp?: Record<string, string | McpRecordConfig>;\n}\n\nexport interface AimockConfig {\n auth?: ApiKeyAuthConfig;\n llm?: {\n fixtures?: string;\n latency?: number;\n chunkSize?: number;\n replaySpeed?: number;\n logLevel?: \"silent\" | \"warn\" | \"info\" | \"debug\";\n chaos?: ChaosConfig;\n /**\n * Server-wide misbehavior defaults. Read only when misbehavior is enabled\n * (`llm.enableMisbehavior: true` or `aimock --misbehavior`); otherwise the\n * key is ignored with a warning, as 1.44.0 ignored it.\n */\n misbehavior?: MisbehaviorConfig | MisbehaviorFaultId;\n /** See `MockServerOptions.enableMisbehavior`. */\n enableMisbehavior?: boolean;\n record?: AimockRecordConfig;\n /**\n * Turns on `llm.record.mcp`. Without it the key is ignored with a warning,\n * as 1.44.0 ignored it.\n */\n enableMcpRecording?: boolean;\n /** See `MockServerOptions.strictToolArguments`. */\n strictToolArguments?: boolean;\n /** See `MockServerOptions.responsesTools`. */\n responsesTools?: ResponsesToolsMode;\n };\n mcp?: MCPConfig;\n a2a?: A2AConfig;\n agui?: AGUIConfig;\n vector?: VectorConfig;\n services?: { search?: boolean; rerank?: boolean; moderate?: boolean };\n metrics?: boolean;\n strict?: boolean;\n port?: number;\n host?: string;\n}\n\n/** C10: the LLM part of `llm.record`. `mcp` is MCP-only; no `providers` means no LLM recording. */\nfunction llmRecordOf(record: AimockRecordConfig | undefined): RecordConfig | undefined {\n if (!record) return undefined;\n const { mcp: _mcp, ...llm } = record;\n void _mcp; // repo pattern for an intentionally unused binding (byteplus-video.ts:1379-1380)\n return llm.providers === undefined ? undefined : llm;\n}\n\nexport function loadConfig(configPath: string): AimockConfig {\n const raw = fs.readFileSync(configPath, \"utf-8\");\n return JSON.parse(raw) as AimockConfig;\n}\n\n/** C3: `llm.record.mcp` is an object with at least one mount. */\nfunction hasMcpRecordMount(mcp: unknown): boolean {\n return (\n typeof mcp === \"object\" && mcp !== null && !Array.isArray(mcp) && Object.keys(mcp).length > 0\n );\n}\n\n/**\n * Command-line overrides for `startFromConfig`. The opt-in flags win over the\n * matching `llm.*` config keys.\n */\nexport interface StartFromConfigOverrides {\n port?: number;\n host?: string;\n /** `aimock --misbehavior`: overrides `llm.enableMisbehavior`. */\n enableMisbehavior?: boolean;\n /** `aimock --strict-tool-arguments`: overrides `llm.strictToolArguments`. */\n strictToolArguments?: boolean;\n /** `aimock --responses-tools`: overrides `llm.responsesTools`. */\n responsesTools?: ResponsesToolsMode;\n}\n\nexport async function startFromConfig(\n config: AimockConfig,\n overrides?: StartFromConfigOverrides,\n): Promise<{ llmock: LLMock; url: string }> {\n const logger = new Logger(\"info\");\n // 1.44.0 ignored these keys, so a non-boolean value (such as the string\n // \"true\") is ignored with a warning, as `llm.misbehavior` is, and the\n // option stays off unless a CLI flag turns it on (`llm.enableMcpRecording`\n // has no CLI flag).\n const booleanOptIn = (\n key: \"enableMisbehavior\" | \"enableMcpRecording\" | \"strictToolArguments\",\n ): boolean | undefined => {\n const value: unknown = config.llm?.[key];\n if (value === undefined || typeof value === \"boolean\") return value;\n logger.warn(\n `Ignoring llm.${key} because it must be true or false, got ${JSON.stringify(value)}.`,\n );\n return undefined;\n };\n const configEnableMisbehavior = booleanOptIn(\"enableMisbehavior\");\n const configStrictToolArguments = booleanOptIn(\"strictToolArguments\");\n // MCP recording also needs an explicit opt-in. 1.44.0 ignored\n // `llm.record.mcp`, so without `llm.enableMcpRecording: true` the key is\n // ignored (with a warning): nothing is mounted or validated and the log level\n // is unchanged. With the opt-in, an invalid value fails startup.\n const enableMcpRecording = booleanOptIn(\"enableMcpRecording\") === true;\n const mcpRecord = enableMcpRecording ? config.llm?.record?.mcp : undefined;\n // Misbehavior needs an explicit opt-in that a 1.44.0 config cannot contain.\n // 1.44.0 ignored `llm.misbehavior`, so without the opt-in the key is ignored\n // (with a warning) and the server serves exactly what 1.44.0 served. With the\n // opt-in, an invalid value fails startup like any other enabled-mode error.\n const enableMisbehavior = (overrides?.enableMisbehavior ?? configEnableMisbehavior) === true;\n let misbehavior: MisbehaviorConfig | undefined;\n if (config.llm?.misbehavior !== undefined) {\n if (!enableMisbehavior) {\n logger.warn(\n \"Ignoring llm.misbehavior because misbehavior is not enabled. Set llm.enableMisbehavior: true or pass --misbehavior to use it.\",\n );\n } else {\n const parsed = parseMisbehavior(config.llm.misbehavior, \"llm.misbehavior\");\n if (!parsed.ok) {\n throw new TypeError(`${parsed.issue.rule}: ${parsed.issue.path}: ${parsed.issue.message}`);\n }\n misbehavior = parsed.config;\n }\n }\n if (config.llm?.record?.mcp !== undefined && !enableMcpRecording) {\n logger.warn(\n \"Ignoring llm.record.mcp because MCP recording is not enabled. Set llm.enableMcpRecording: true to use it.\",\n );\n }\n\n // 1.44.0 ignored `llm.responsesTools`, so an invalid value is ignored with a\n // warning (as `llm.misbehavior` is) and the server runs in \"legacy\" mode,\n // unless `--responses-tools` sets the mode. The warning names the mode that\n // actually runs.\n let responsesTools = config.llm?.responsesTools;\n if (\n responsesTools !== undefined &&\n responsesTools !== \"legacy\" &&\n responsesTools !== \"extended\"\n ) {\n const effective =\n overrides?.responsesTools !== undefined\n ? `\"${overrides.responsesTools}\" from --responses-tools`\n : '\"legacy\"';\n logger.warn(\n `Ignoring llm.responsesTools because it must be \"legacy\" or \"extended\", got ${JSON.stringify(responsesTools)}. Using ${effective}.`,\n );\n responsesTools = undefined;\n }\n\n // A non-positive replaySpeed fails calculateDelay's `speed > 0` check and applies the\n // full delay rather than none. Mirrors the fixture-level guard in fixture-loader.ts.\n let replaySpeed = config.llm?.replaySpeed;\n if (replaySpeed != null && (!Number.isFinite(replaySpeed) || replaySpeed <= 0)) {\n logger.warn(`llm.replaySpeed must be a positive number, got ${replaySpeed}. Ignoring.`);\n replaySpeed = undefined;\n }\n\n // Load fixtures if specified\n const resolvedAuth = resolveInboundAuth(selectInboundAuthSource(config.auth));\n const llmock = createLLMockWithResolvedAuth(\n {\n port: overrides?.port ?? config.port ?? 0,\n host: overrides?.host ?? config.host ?? \"127.0.0.1\",\n latency: config.llm?.latency,\n chunkSize: config.llm?.chunkSize,\n replaySpeed,\n // llm.record.mcp reports skipped or failed recordings only as MCP-RECORD: warnings,\n // so an enabled config with at least one mount defaults to \"warn\" rather than the silent\n // default. An empty llm.record.mcp records nothing. An explicit logLevel wins.\n logLevel: config.llm?.logLevel ?? (hasMcpRecordMount(mcpRecord) ? \"warn\" : undefined),\n chaos: config.llm?.chaos,\n misbehavior,\n ...(enableMisbehavior ? { enableMisbehavior: true } : {}),\n record: llmRecordOf(config.llm?.record),\n metrics: config.metrics,\n strict: config.strict,\n strictToolArguments: overrides?.strictToolArguments ?? configStrictToolArguments,\n responsesTools: overrides?.responsesTools ?? responsesTools,\n },\n resolvedAuth,\n );\n\n // What this function mounted, by path (MR1: llm.record.mcp records through an MCP mount).\n const configMounts = new Map<string, Mountable>();\n\n if (config.llm?.fixtures) {\n const fixturePath = path.resolve(config.llm.fixtures);\n const stat = fs.statSync(fixturePath);\n if (stat.isDirectory()) {\n llmock.loadFixtureDir(fixturePath);\n } else {\n llmock.loadFixtureFile(fixturePath);\n }\n }\n\n // MCP\n if (config.mcp) {\n const mcpConfig = config.mcp;\n const mcp = new MCPMock({\n serverInfo: mcpConfig.serverInfo,\n });\n\n if (mcpConfig.tools) {\n for (const tool of mcpConfig.tools) {\n const { result, ...def } = tool;\n mcp.addTool(def);\n if (result !== undefined) {\n const answer = () => result;\n configToolHandlers.add(answer);\n mcp.onToolCall(def.name, answer);\n }\n }\n }\n\n if (mcpConfig.resources) {\n for (const res of mcpConfig.resources) {\n mcp.addResource(\n { uri: res.uri, name: res.name, mimeType: res.mimeType, description: res.description },\n res.text !== undefined || res.blob !== undefined\n ? { text: res.text, blob: res.blob, mimeType: res.mimeType }\n : undefined,\n );\n }\n }\n\n if (mcpConfig.prompts) {\n for (const prompt of mcpConfig.prompts) {\n const { result, ...def } = prompt;\n if (result) {\n mcp.addPrompt(def, () => result as import(\"./mcp-types.js\").MCPPromptResult);\n } else {\n mcp.addPrompt(def);\n }\n }\n }\n\n const mcpPath = mcpConfig.path ?? \"/mcp\";\n llmock.mount(mcpPath, mcp);\n configMounts.set(mcpPath, mcp);\n logger.info(`MCPMock mounted at ${mcpPath}`);\n }\n\n // A2A\n if (config.a2a) {\n const a2aConfig = config.a2a;\n const a2a = new A2AMock();\n\n if (a2aConfig.agents) {\n for (const agentConfig of a2aConfig.agents) {\n const { messages, tasks, streamingTasks, ...def } = agentConfig;\n a2a.registerAgent(def);\n\n if (messages) {\n for (const m of messages) {\n a2a.onMessage(def.name, m.pattern, m.parts ?? [{ text: \"\" }]);\n }\n }\n\n if (tasks) {\n for (const t of tasks) {\n a2a.onTask(def.name, t.pattern, t.artifacts ?? []);\n }\n }\n\n if (streamingTasks) {\n for (const s of streamingTasks) {\n a2a.onStreamingTask(def.name, s.pattern, s.events ?? [], s.delayMs);\n }\n }\n }\n }\n\n const a2aPath = a2aConfig.path ?? \"/a2a\";\n llmock.mount(a2aPath, a2a);\n configMounts.set(a2aPath, a2a);\n logger.info(`A2AMock mounted at ${a2aPath}`);\n }\n\n // AG-UI\n if (config.agui) {\n const aguiConfig = config.agui;\n const agui = new AGUIMock();\n\n if (aguiConfig.fixtures) {\n for (const f of aguiConfig.fixtures) {\n if (f.match.toolCallId && f.text && !f.events) {\n logger.warn(\n `AG-UI fixture uses text shorthand with toolCallId — text shorthand ignores toolCallId matching; use events[] instead (match: ${JSON.stringify(f.match)})`,\n );\n }\n if (f.text) {\n if (f.match.message !== undefined) {\n agui.onMessage(f.match.message, f.text, { delayMs: f.delayMs });\n } else {\n // No message pattern — register via addFixture so it only matches\n // on other criteria (toolCallId, toolName, stateKey) instead of\n // becoming a catch-all that matches every request.\n agui.addFixture({\n match: {\n toolCallId: f.match.toolCallId,\n toolName: f.match.toolName,\n stateKey: f.match.stateKey,\n },\n events: buildTextResponse(f.text),\n delayMs: f.delayMs,\n });\n }\n } else if (f.events) {\n agui.addFixture({\n match: {\n message: f.match.message,\n toolCallId: f.match.toolCallId,\n toolName: f.match.toolName,\n stateKey: f.match.stateKey,\n },\n events: f.events,\n delayMs: f.delayMs,\n });\n } else {\n logger.warn(\n `AG-UI fixture has neither text nor events — it will be skipped (match: ${JSON.stringify(f.match)})`,\n );\n }\n }\n }\n\n const aguiPath = aguiConfig.path ?? \"/agui\";\n llmock.mount(aguiPath, agui);\n configMounts.set(aguiPath, agui);\n logger.info(`AGUIMock mounted at ${aguiPath}`);\n }\n\n // Vector\n if (config.vector) {\n const vectorConfig = config.vector;\n const vector = new VectorMock();\n\n if (vectorConfig.collections) {\n for (const col of vectorConfig.collections) {\n vector.addCollection(col.name, { dimension: col.dimension });\n\n if (col.vectors && col.vectors.length > 0) {\n vector.upsert(col.name, col.vectors);\n }\n\n if (col.queryResults) {\n vector.onQuery(col.name, col.queryResults);\n }\n }\n }\n\n const vectorPath = vectorConfig.path ?? \"/vector\";\n llmock.mount(vectorPath, vector);\n configMounts.set(vectorPath, vector);\n logger.info(`VectorMock mounted at ${vectorPath}`);\n }\n\n // MR1: llm.record.mcp — record mounts (auto-mount an MCPMock where none is).\n // Read only with llm.enableMcpRecording: true. Any value that is present is then\n // validated, so null, false, 0 and \"\" fail at start too.\n if (mcpRecord !== undefined) {\n wireMcpRecording(llmock, mcpRecord, {\n mounted: configMounts,\n mcpMock: MCPMock,\n fixtures: config.llm?.fixtures,\n fixturePath: config.llm?.record?.fixturePath,\n strict: config.strict,\n env: process.env,\n });\n }\n\n // Services — configure default catch-all responses\n if (config.services) {\n if (config.services.search) {\n llmock.onSearch(/.*/, []);\n logger.info(\"Search service enabled with default empty results\");\n }\n if (config.services.rerank) {\n llmock.onRerank(/.*/, []);\n logger.info(\"Rerank service enabled with default empty results\");\n }\n if (config.services.moderate) {\n llmock.onModerate(/.*/, { flagged: false, categories: {} });\n logger.info(\"Moderation service enabled with default unflagged results\");\n }\n }\n\n const url = await llmock.start();\n return { llmock, url };\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
const require_mcp_fakes = require('./mcp-fakes.cjs');
|
|
2
|
+
const require_mcp_fakes_report = require('./mcp-fakes-report.cjs');
|
|
3
|
+
|
|
4
|
+
//#region src/fakes-plugin.ts
|
|
5
|
+
/** TI1/TI2 separator: space, U+203A, space. */
|
|
6
|
+
const TEST_ID_SEPARATOR = " › ";
|
|
7
|
+
const FAKES_REPORT_MODES = [
|
|
8
|
+
"off",
|
|
9
|
+
"warn",
|
|
10
|
+
"fail"
|
|
11
|
+
];
|
|
12
|
+
/** The `fakesReport` option, checked: an unknown mode fails `beforeAll`. */
|
|
13
|
+
function fakesReportMode(mode) {
|
|
14
|
+
if (mode === void 0) return "off";
|
|
15
|
+
if (!FAKES_REPORT_MODES.includes(mode)) throw new Error(`useAimock(): fakesReport must be one of ${FAKES_REPORT_MODES.map((m) => JSON.stringify(m)).join(", ")}; got ${JSON.stringify(mode)}`);
|
|
16
|
+
return mode;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* The URL and headers that reach the fakes scoped to `testId` (and
|
|
20
|
+
* `opts.context`) at `opts.mount`. `URLSearchParams` writes a space as `+`,
|
|
21
|
+
* which the mount reads back as a space in the query.
|
|
22
|
+
*/
|
|
23
|
+
function fakesTarget(url, testId, opts = {}) {
|
|
24
|
+
const mount = opts.mount ?? "/mcp";
|
|
25
|
+
const query = new URLSearchParams({ testId });
|
|
26
|
+
const headers = { "X-Test-Id": encodeURIComponent(testId) };
|
|
27
|
+
if (opts.context) {
|
|
28
|
+
query.set("context", opts.context);
|
|
29
|
+
headers["X-AIMock-Context"] = encodeURIComponent(opts.context);
|
|
30
|
+
}
|
|
31
|
+
return {
|
|
32
|
+
testId,
|
|
33
|
+
mcpUrl: `${url}${mount}?${query.toString()}`,
|
|
34
|
+
headers
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
/** RP6: read `GET /__aimock/mcp/fakes/report` for one identity. */
|
|
38
|
+
async function fetchFakesReport(url, testId, context) {
|
|
39
|
+
const query = new URLSearchParams();
|
|
40
|
+
if (testId !== null) query.set("testId", testId);
|
|
41
|
+
if (context !== null) query.set("context", context);
|
|
42
|
+
const res = await fetch(`${url}/__aimock/mcp/fakes/report?${query.toString()}`);
|
|
43
|
+
if (!res.ok) throw new Error(`aimock fakes report: HTTP ${res.status} ${await res.text()}`);
|
|
44
|
+
return await res.json();
|
|
45
|
+
}
|
|
46
|
+
function identityKey(testId, context) {
|
|
47
|
+
return `${testId ?? ""}\u0000${context ?? ""}`;
|
|
48
|
+
}
|
|
49
|
+
/** Per-`useAimock` state shared by the Vitest and Jest adapters. */
|
|
50
|
+
var FakesPluginState = class {
|
|
51
|
+
registered = /* @__PURE__ */ new Map();
|
|
52
|
+
/** RP7 (b): id of the last journal entry when the test started (C2). */
|
|
53
|
+
marker = null;
|
|
54
|
+
constructor(mode) {
|
|
55
|
+
this.mode = mode;
|
|
56
|
+
}
|
|
57
|
+
/** Start of a test, after the match-count reset. */
|
|
58
|
+
beforeEach(llm) {
|
|
59
|
+
this.registered.clear();
|
|
60
|
+
this.marker = llm.getLastRequest()?.id ?? null;
|
|
61
|
+
}
|
|
62
|
+
/** RP7 (a): `fakesFor` registers the identity for the current test. */
|
|
63
|
+
register(testId, context) {
|
|
64
|
+
this.registered.set(identityKey(testId, context), {
|
|
65
|
+
testId,
|
|
66
|
+
context
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* RP7 (a) and (b): the registered identities, plus every (test id, context)
|
|
71
|
+
* an MCP request carried since the marker. If the marker entry is gone
|
|
72
|
+
* (FIFO cap or a cleared journal), every remaining entry is newer.
|
|
73
|
+
*/
|
|
74
|
+
identities(llm) {
|
|
75
|
+
const all = llm.getRequests();
|
|
76
|
+
const at = this.marker === null ? -1 : all.findIndex((e) => e.id === this.marker);
|
|
77
|
+
const newer = at >= 0 ? all.slice(at + 1) : all;
|
|
78
|
+
const out = new Map(this.registered);
|
|
79
|
+
for (const entry of newer) {
|
|
80
|
+
if (entry.service !== "mcp" || entry.testId === void 0) continue;
|
|
81
|
+
const testId = entry.testId === require_mcp_fakes.MCP_FAKES_DEFAULT_TEST_ID ? null : entry.testId;
|
|
82
|
+
const context = entry.context ?? null;
|
|
83
|
+
out.set(identityKey(testId, context), {
|
|
84
|
+
testId,
|
|
85
|
+
context
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
return [...out.values()];
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* End of a test, before the next reset: read each identity's report.
|
|
92
|
+
* Under `"fail"`, a report with `ok: false` throws `AimockFakesReportError`
|
|
93
|
+
* (the first failing report, with every failing report's text); under
|
|
94
|
+
* `"warn"`, the text goes to `console.warn`. An unconsumed entry two
|
|
95
|
+
* reports share (an entry scoped by test id only, checked with and without
|
|
96
|
+
* a context) is listed once.
|
|
97
|
+
*/
|
|
98
|
+
async afterEach(llm, url) {
|
|
99
|
+
if (this.mode === "off") return;
|
|
100
|
+
const failing = [];
|
|
101
|
+
const lines = [];
|
|
102
|
+
const unconsumedSeen = /* @__PURE__ */ new Set();
|
|
103
|
+
for (const id of this.identities(llm)) {
|
|
104
|
+
const report = await fetchFakesReport(url, id.testId, id.context);
|
|
105
|
+
if (report.ok) continue;
|
|
106
|
+
failing.push(report);
|
|
107
|
+
for (const line of require_mcp_fakes_report.formatFakesReport(report).split("\n")) {
|
|
108
|
+
if (line.startsWith(" unconsumed: ")) {
|
|
109
|
+
if (unconsumedSeen.has(line)) continue;
|
|
110
|
+
unconsumedSeen.add(line);
|
|
111
|
+
}
|
|
112
|
+
lines.push(line);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
if (failing.length === 0) return;
|
|
116
|
+
const text = lines.join("\n");
|
|
117
|
+
if (this.mode === "warn") {
|
|
118
|
+
console.warn(text);
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
throw new require_mcp_fakes_report.AimockFakesReportError(failing[0], text);
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
//#endregion
|
|
126
|
+
exports.FakesPluginState = FakesPluginState;
|
|
127
|
+
exports.TEST_ID_SEPARATOR = TEST_ID_SEPARATOR;
|
|
128
|
+
exports.fakesReportMode = fakesReportMode;
|
|
129
|
+
exports.fakesTarget = fakesTarget;
|
|
130
|
+
exports.fetchFakesReport = fetchFakesReport;
|
|
131
|
+
//# sourceMappingURL=fakes-plugin.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fakes-plugin.cjs","names":["MCP_FAKES_DEFAULT_TEST_ID","formatFakesReport","AimockFakesReportError"],"sources":["../src/fakes-plugin.ts"],"sourcesContent":["/**\n * The MCP fakes part of the Vitest and Jest plugins (record-replay RP7, TI):\n * `fakesFor` targets, the per-test fake report check in `afterEach`, and the\n * state both adapters share. The default test id itself is built by each\n * adapter (TI1 Vitest, TI2 Jest), and it is used only by the fakes helpers\n * (TI4): nothing here tags LLM or control traffic.\n */\nimport type { LLMock } from \"./llmock.js\";\nimport {\n AimockFakesReportError,\n formatFakesReport,\n type McpFakesReport,\n} from \"./mcp-fakes-report.js\";\nimport { MCP_FAKES_DEFAULT_TEST_ID } from \"./mcp-fakes.js\";\n\n/** RP7: what `afterEach` does with a failing report. `\"off\"` is the default. */\nexport type FakesReportMode = \"off\" | \"warn\" | \"fail\";\n\n/** RP7: what `fakesFor` returns, for an MCP client. */\nexport interface FakesTarget {\n /** The test id the URL and headers carry (explicit, or the inferred default). */\n testId: string;\n /** The mount URL with `?testId=` (and `&context=`). */\n mcpUrl: string;\n /** The matching `X-Test-Id` / `X-AIMock-Context` headers, URI-encoded. */\n headers: Record<string, string>;\n}\n\nexport interface FakesForOptions {\n /** The context the fake blocks are scoped to. */\n context?: string;\n /** The MCP mount path (default `\"/mcp\"`). */\n mount?: string;\n}\n\n/** TI1/TI2 separator: space, U+203A, space. */\nexport const TEST_ID_SEPARATOR = \" › \";\n\nconst FAKES_REPORT_MODES: readonly FakesReportMode[] = [\"off\", \"warn\", \"fail\"];\n\n/** The `fakesReport` option, checked: an unknown mode fails `beforeAll`. */\nexport function fakesReportMode(mode: FakesReportMode | undefined): FakesReportMode {\n if (mode === undefined) return \"off\";\n if (!FAKES_REPORT_MODES.includes(mode)) {\n throw new Error(\n `useAimock(): fakesReport must be one of ${FAKES_REPORT_MODES.map((m) => JSON.stringify(m)).join(\", \")}; got ${JSON.stringify(mode)}`,\n );\n }\n return mode;\n}\n\n/**\n * The URL and headers that reach the fakes scoped to `testId` (and\n * `opts.context`) at `opts.mount`. `URLSearchParams` writes a space as `+`,\n * which the mount reads back as a space in the query.\n */\nexport function fakesTarget(url: string, testId: string, opts: FakesForOptions = {}): FakesTarget {\n const mount = opts.mount ?? \"/mcp\";\n const query = new URLSearchParams({ testId });\n const headers: Record<string, string> = { \"X-Test-Id\": encodeURIComponent(testId) };\n if (opts.context) {\n query.set(\"context\", opts.context);\n headers[\"X-AIMock-Context\"] = encodeURIComponent(opts.context);\n }\n return { testId, mcpUrl: `${url}${mount}?${query.toString()}`, headers };\n}\n\n/** RP6: read `GET /__aimock/mcp/fakes/report` for one identity. */\nexport async function fetchFakesReport(\n url: string,\n testId: string | null,\n context: string | null,\n): Promise<McpFakesReport> {\n const query = new URLSearchParams();\n if (testId !== null) query.set(\"testId\", testId);\n if (context !== null) query.set(\"context\", context);\n const res = await fetch(`${url}/__aimock/mcp/fakes/report?${query.toString()}`);\n if (!res.ok) {\n throw new Error(`aimock fakes report: HTTP ${res.status} ${await res.text()}`);\n }\n return (await res.json()) as McpFakesReport;\n}\n\n/** One identity the report checks; `null` means none supplied. */\nexport interface FakesIdentity {\n testId: string | null;\n context: string | null;\n}\n\nfunction identityKey(testId: string | null, context: string | null): string {\n return `${testId ?? \"\"}\\u0000${context ?? \"\"}`;\n}\n\n/** Per-`useAimock` state shared by the Vitest and Jest adapters. */\nexport class FakesPluginState {\n private readonly registered = new Map<string, FakesIdentity>();\n /** RP7 (b): id of the last journal entry when the test started (C2). */\n private marker: string | null = null;\n\n constructor(readonly mode: FakesReportMode) {}\n\n /** Start of a test, after the match-count reset. */\n beforeEach(llm: LLMock): void {\n this.registered.clear();\n this.marker = llm.getLastRequest()?.id ?? null;\n }\n\n /** RP7 (a): `fakesFor` registers the identity for the current test. */\n register(testId: string, context: string | null): void {\n this.registered.set(identityKey(testId, context), { testId, context });\n }\n\n /**\n * RP7 (a) and (b): the registered identities, plus every (test id, context)\n * an MCP request carried since the marker. If the marker entry is gone\n * (FIFO cap or a cleared journal), every remaining entry is newer.\n */\n identities(llm: LLMock): FakesIdentity[] {\n const all = llm.getRequests();\n const at = this.marker === null ? -1 : all.findIndex((e) => e.id === this.marker);\n const newer = at >= 0 ? all.slice(at + 1) : all;\n const out = new Map(this.registered);\n for (const entry of newer) {\n if (entry.service !== \"mcp\" || entry.testId === undefined) continue;\n const testId = entry.testId === MCP_FAKES_DEFAULT_TEST_ID ? null : entry.testId;\n const context = entry.context ?? null;\n out.set(identityKey(testId, context), { testId, context });\n }\n return [...out.values()];\n }\n\n /**\n * End of a test, before the next reset: read each identity's report.\n * Under `\"fail\"`, a report with `ok: false` throws `AimockFakesReportError`\n * (the first failing report, with every failing report's text); under\n * `\"warn\"`, the text goes to `console.warn`. An unconsumed entry two\n * reports share (an entry scoped by test id only, checked with and without\n * a context) is listed once.\n */\n async afterEach(llm: LLMock, url: string): Promise<void> {\n if (this.mode === \"off\") return;\n const failing: McpFakesReport[] = [];\n const lines: string[] = [];\n const unconsumedSeen = new Set<string>();\n for (const id of this.identities(llm)) {\n const report = await fetchFakesReport(url, id.testId, id.context);\n if (report.ok) continue;\n failing.push(report);\n for (const line of formatFakesReport(report).split(\"\\n\")) {\n if (line.startsWith(\" unconsumed: \")) {\n if (unconsumedSeen.has(line)) continue;\n unconsumedSeen.add(line);\n }\n lines.push(line);\n }\n }\n if (failing.length === 0) return;\n const text = lines.join(\"\\n\");\n if (this.mode === \"warn\") {\n console.warn(text);\n return;\n }\n throw new AimockFakesReportError(failing[0], text);\n }\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
//#region src/fakes-plugin.d.ts
|
|
2
|
+
|
|
3
|
+
/** RP7: what `afterEach` does with a failing report. `"off"` is the default. */
|
|
4
|
+
type FakesReportMode = "off" | "warn" | "fail";
|
|
5
|
+
/** RP7: what `fakesFor` returns, for an MCP client. */
|
|
6
|
+
interface FakesTarget {
|
|
7
|
+
/** The test id the URL and headers carry (explicit, or the inferred default). */
|
|
8
|
+
testId: string;
|
|
9
|
+
/** The mount URL with `?testId=` (and `&context=`). */
|
|
10
|
+
mcpUrl: string;
|
|
11
|
+
/** The matching `X-Test-Id` / `X-AIMock-Context` headers, URI-encoded. */
|
|
12
|
+
headers: Record<string, string>;
|
|
13
|
+
}
|
|
14
|
+
interface FakesForOptions {
|
|
15
|
+
/** The context the fake blocks are scoped to. */
|
|
16
|
+
context?: string;
|
|
17
|
+
/** The MCP mount path (default `"/mcp"`). */
|
|
18
|
+
mount?: string;
|
|
19
|
+
}
|
|
20
|
+
/** TI1/TI2 separator: space, U+203A, space. */
|
|
21
|
+
//#endregion
|
|
22
|
+
export { FakesForOptions, FakesReportMode, FakesTarget };
|
|
23
|
+
//# sourceMappingURL=fakes-plugin.d.cts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fakes-plugin.d.cts","names":[],"sources":["../src/fakes-plugin.ts"],"sourcesContent":[],"mappings":";;;KAgBY,eAAA;;UAGK,WAAA;;;;;;WAMN;;UAGM,eAAA"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
//#region src/fakes-plugin.d.ts
|
|
2
|
+
|
|
3
|
+
/** RP7: what `afterEach` does with a failing report. `"off"` is the default. */
|
|
4
|
+
type FakesReportMode = "off" | "warn" | "fail";
|
|
5
|
+
/** RP7: what `fakesFor` returns, for an MCP client. */
|
|
6
|
+
interface FakesTarget {
|
|
7
|
+
/** The test id the URL and headers carry (explicit, or the inferred default). */
|
|
8
|
+
testId: string;
|
|
9
|
+
/** The mount URL with `?testId=` (and `&context=`). */
|
|
10
|
+
mcpUrl: string;
|
|
11
|
+
/** The matching `X-Test-Id` / `X-AIMock-Context` headers, URI-encoded. */
|
|
12
|
+
headers: Record<string, string>;
|
|
13
|
+
}
|
|
14
|
+
interface FakesForOptions {
|
|
15
|
+
/** The context the fake blocks are scoped to. */
|
|
16
|
+
context?: string;
|
|
17
|
+
/** The MCP mount path (default `"/mcp"`). */
|
|
18
|
+
mount?: string;
|
|
19
|
+
}
|
|
20
|
+
/** TI1/TI2 separator: space, U+203A, space. */
|
|
21
|
+
//#endregion
|
|
22
|
+
export { FakesForOptions, FakesReportMode, FakesTarget };
|
|
23
|
+
//# sourceMappingURL=fakes-plugin.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fakes-plugin.d.ts","names":[],"sources":["../src/fakes-plugin.ts"],"sourcesContent":[],"mappings":";;;KAgBY,eAAA;;UAGK,WAAA;;;;;;WAMN;;UAGM,eAAA"}
|