@copilotkit/aimock 1.37.4 → 1.39.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/CHANGELOG.md +48 -0
- package/README.md +50 -11
- package/dist/agui-handler.cjs +20 -18
- package/dist/agui-handler.cjs.map +1 -1
- package/dist/agui-handler.d.cts +8 -1
- package/dist/agui-handler.d.cts.map +1 -1
- package/dist/agui-handler.d.ts +8 -1
- package/dist/agui-handler.d.ts.map +1 -1
- package/dist/agui-handler.js +20 -18
- package/dist/agui-handler.js.map +1 -1
- package/dist/agui-recorder.cjs +6 -0
- package/dist/agui-recorder.cjs.map +1 -1
- package/dist/agui-recorder.d.cts.map +1 -1
- package/dist/agui-recorder.d.ts.map +1 -1
- package/dist/agui-recorder.js +6 -0
- package/dist/agui-recorder.js.map +1 -1
- package/dist/agui-stub.d.cts +2 -2
- package/dist/agui-stub.d.ts +2 -2
- package/dist/agui-types.d.cts +19 -1
- package/dist/agui-types.d.cts.map +1 -1
- package/dist/agui-types.d.ts +19 -1
- package/dist/agui-types.d.ts.map +1 -1
- package/dist/aimock-cli.cjs +1 -1
- package/dist/aimock-cli.cjs.map +1 -1
- package/dist/aimock-cli.js +1 -1
- package/dist/aimock-cli.js.map +1 -1
- package/dist/api-key-auth.cjs +166 -0
- package/dist/api-key-auth.cjs.map +1 -0
- package/dist/api-key-auth.d.cts +17 -0
- package/dist/api-key-auth.d.cts.map +1 -0
- package/dist/api-key-auth.d.ts +17 -0
- package/dist/api-key-auth.d.ts.map +1 -0
- package/dist/api-key-auth.js +158 -0
- package/dist/api-key-auth.js.map +1 -0
- package/dist/cli.cjs +14 -11
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +14 -11
- package/dist/cli.js.map +1 -1
- package/dist/config-loader.cjs +4 -2
- package/dist/config-loader.cjs.map +1 -1
- package/dist/config-loader.d.cts +2 -1
- package/dist/config-loader.d.cts.map +1 -1
- package/dist/config-loader.d.ts +2 -1
- package/dist/config-loader.d.ts.map +1 -1
- package/dist/config-loader.js +5 -3
- package/dist/config-loader.js.map +1 -1
- package/dist/fal-audio.cjs +2 -0
- package/dist/fal-audio.cjs.map +1 -1
- package/dist/fal-audio.d.cts.map +1 -1
- package/dist/fal-audio.d.ts.map +1 -1
- package/dist/fal-audio.js +2 -0
- package/dist/fal-audio.js.map +1 -1
- package/dist/fal.cjs +4 -1
- package/dist/fal.cjs.map +1 -1
- package/dist/fal.d.cts.map +1 -1
- package/dist/fal.d.ts.map +1 -1
- package/dist/fal.js +4 -1
- package/dist/fal.js.map +1 -1
- package/dist/fixture-loader.cjs +0 -0
- package/dist/fixture-loader.cjs.map +1 -1
- package/dist/fixture-loader.d.cts +3 -1
- package/dist/fixture-loader.d.cts.map +1 -1
- package/dist/fixture-loader.d.ts +3 -1
- package/dist/fixture-loader.d.ts.map +1 -1
- package/dist/fixture-loader.js +0 -0
- package/dist/fixture-loader.js.map +1 -1
- package/dist/grok-video.cjs +9 -2
- package/dist/grok-video.cjs.map +1 -1
- package/dist/grok-video.js +10 -3
- package/dist/grok-video.js.map +1 -1
- package/dist/helpers.cjs +16 -22
- package/dist/helpers.cjs.map +1 -1
- package/dist/helpers.d.cts.map +1 -1
- package/dist/helpers.d.ts.map +1 -1
- package/dist/helpers.js +16 -21
- package/dist/helpers.js.map +1 -1
- package/dist/index.d.cts +3 -3
- package/dist/index.d.ts +3 -3
- package/dist/llmock.cjs +37 -12
- package/dist/llmock.cjs.map +1 -1
- package/dist/llmock.d.cts +26 -2
- package/dist/llmock.d.cts.map +1 -1
- package/dist/llmock.d.ts +26 -2
- package/dist/llmock.d.ts.map +1 -1
- package/dist/llmock.js +39 -15
- package/dist/llmock.js.map +1 -1
- package/dist/openrouter-chat.cjs +333 -0
- package/dist/openrouter-chat.cjs.map +1 -0
- package/dist/openrouter-chat.js +325 -0
- package/dist/openrouter-chat.js.map +1 -0
- package/dist/openrouter-video.cjs +24 -7
- 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 +25 -8
- package/dist/openrouter-video.js.map +1 -1
- package/dist/provider-auth.cjs +20 -0
- package/dist/provider-auth.cjs.map +1 -1
- package/dist/provider-auth.js +20 -1
- package/dist/provider-auth.js.map +1 -1
- package/dist/recorder.cjs +50 -8
- package/dist/recorder.cjs.map +1 -1
- package/dist/recorder.d.cts.map +1 -1
- package/dist/recorder.d.ts.map +1 -1
- package/dist/recorder.js +50 -10
- package/dist/recorder.js.map +1 -1
- package/dist/responses.cjs +200 -89
- package/dist/responses.cjs.map +1 -1
- package/dist/responses.d.cts +18 -3
- package/dist/responses.d.cts.map +1 -1
- package/dist/responses.d.ts +18 -3
- package/dist/responses.d.ts.map +1 -1
- package/dist/responses.js +199 -90
- package/dist/responses.js.map +1 -1
- package/dist/server.cjs +190 -83
- package/dist/server.cjs.map +1 -1
- package/dist/server.d.cts +7 -2
- package/dist/server.d.cts.map +1 -1
- package/dist/server.d.ts +7 -2
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +190 -85
- package/dist/server.js.map +1 -1
- package/dist/sse-writer.cjs +3 -0
- package/dist/sse-writer.cjs.map +1 -1
- package/dist/sse-writer.d.cts +8 -0
- package/dist/sse-writer.d.cts.map +1 -1
- package/dist/sse-writer.d.ts +8 -0
- package/dist/sse-writer.d.ts.map +1 -1
- package/dist/sse-writer.js +3 -0
- package/dist/sse-writer.js.map +1 -1
- package/dist/stream-collapse.cjs +103 -0
- package/dist/stream-collapse.cjs.map +1 -1
- package/dist/stream-collapse.d.cts +7 -0
- package/dist/stream-collapse.d.cts.map +1 -1
- package/dist/stream-collapse.d.ts +7 -0
- package/dist/stream-collapse.d.ts.map +1 -1
- package/dist/stream-collapse.js +103 -0
- package/dist/stream-collapse.js.map +1 -1
- package/dist/transcription.cjs +49 -2
- package/dist/transcription.cjs.map +1 -1
- package/dist/transcription.d.cts.map +1 -1
- package/dist/transcription.d.ts.map +1 -1
- package/dist/transcription.js +50 -3
- package/dist/transcription.js.map +1 -1
- package/dist/types.d.cts +187 -5
- package/dist/types.d.cts.map +1 -1
- package/dist/types.d.ts +187 -5
- package/dist/types.d.ts.map +1 -1
- package/dist/vector-types.d.ts.map +1 -1
- package/dist/veo-video.cjs +9 -2
- package/dist/veo-video.cjs.map +1 -1
- package/dist/veo-video.js +10 -3
- package/dist/veo-video.js.map +1 -1
- package/dist/ws-gemini-live.cjs +41 -6
- package/dist/ws-gemini-live.cjs.map +1 -1
- package/dist/ws-gemini-live.js +41 -6
- package/dist/ws-gemini-live.js.map +1 -1
- package/dist/ws-realtime.cjs +306 -57
- package/dist/ws-realtime.cjs.map +1 -1
- package/dist/ws-realtime.d.cts +1 -0
- package/dist/ws-realtime.d.cts.map +1 -1
- package/dist/ws-realtime.d.ts +1 -0
- package/dist/ws-realtime.d.ts.map +1 -1
- package/dist/ws-realtime.js +307 -58
- package/dist/ws-realtime.js.map +1 -1
- package/dist/ws-responses.cjs +11 -6
- package/dist/ws-responses.cjs.map +1 -1
- package/dist/ws-responses.d.cts.map +1 -1
- package/dist/ws-responses.d.ts.map +1 -1
- package/dist/ws-responses.js +12 -7
- package/dist/ws-responses.js.map +1 -1
- package/package.json +3 -2
- package/skills/write-fixtures/SKILL.md +53 -19
package/dist/agui-types.d.cts
CHANGED
|
@@ -18,11 +18,13 @@ interface AGUIRunFinishedEvent extends AGUIBaseEvent {
|
|
|
18
18
|
runId: string;
|
|
19
19
|
result?: unknown;
|
|
20
20
|
outcome?: AGUIRunFinishedOutcome;
|
|
21
|
+
usage?: AGUITokenUsage[];
|
|
21
22
|
}
|
|
22
23
|
interface AGUIRunErrorEvent extends AGUIBaseEvent {
|
|
23
24
|
type: "RUN_ERROR";
|
|
24
25
|
message: string;
|
|
25
26
|
code?: string;
|
|
27
|
+
usage?: AGUITokenUsage[];
|
|
26
28
|
}
|
|
27
29
|
interface AGUIStepStartedEvent extends AGUIBaseEvent {
|
|
28
30
|
type: "STEP_STARTED";
|
|
@@ -192,6 +194,22 @@ type AGUIRunFinishedOutcome = {
|
|
|
192
194
|
type: "interrupt";
|
|
193
195
|
interrupts: AGUIInterrupt[];
|
|
194
196
|
};
|
|
197
|
+
/**
|
|
198
|
+
* Numeric-only token usage summary, mirroring `TokenUsageSchema` in
|
|
199
|
+
* `@ag-ui/core`. Deliberately carries no content-bearing or identifying
|
|
200
|
+
* fields — only provider/model labels and non-negative integer token counts.
|
|
201
|
+
* Carried as an array on RUN_FINISHED / RUN_ERROR so a run that invokes
|
|
202
|
+
* multiple models keeps them separate.
|
|
203
|
+
*/
|
|
204
|
+
interface AGUITokenUsage {
|
|
205
|
+
provider?: string;
|
|
206
|
+
model?: string;
|
|
207
|
+
inputTokens?: number;
|
|
208
|
+
outputTokens?: number;
|
|
209
|
+
totalTokens?: number;
|
|
210
|
+
reasoningTokens?: number;
|
|
211
|
+
cachedInputTokens?: number;
|
|
212
|
+
}
|
|
195
213
|
interface AGUIRunAgentInput {
|
|
196
214
|
threadId: string;
|
|
197
215
|
runId: string;
|
|
@@ -275,5 +293,5 @@ interface AGUIRecordConfig {
|
|
|
275
293
|
}
|
|
276
294
|
//# sourceMappingURL=agui-types.d.ts.map
|
|
277
295
|
//#endregion
|
|
278
|
-
export { AGUIActivityDeltaEvent, AGUIActivitySnapshotEvent, AGUIBaseEvent, AGUICustomEvent, AGUIEvent, AGUIEventType, AGUIFixture, AGUIFixtureMatch, AGUIInterrupt, AGUIMessage, AGUIMessageContentPart, AGUIMessageRole, AGUIMessagesSnapshotEvent, AGUIMockOptions, AGUIRawEvent, AGUIReasoningEncryptedValueEvent, AGUIReasoningEncryptedValueSubtype, AGUIReasoningEndEvent, AGUIReasoningMessageChunkEvent, AGUIReasoningMessageContentEvent, AGUIReasoningMessageEndEvent, AGUIReasoningMessageStartEvent, AGUIReasoningStartEvent, AGUIRecordConfig, AGUIResumeEntry, AGUIRunAgentInput, AGUIRunErrorEvent, AGUIRunFinishedEvent, AGUIRunFinishedOutcome, AGUIRunStartedEvent, AGUIStateDeltaEvent, AGUIStateSnapshotEvent, AGUIStepFinishedEvent, AGUIStepStartedEvent, AGUITextMessageChunkEvent, AGUITextMessageContentEvent, AGUITextMessageEndEvent, AGUITextMessageRole, AGUITextMessageStartEvent, AGUIThinkingEndEvent, AGUIThinkingStartEvent, AGUIThinkingTextMessageContentEvent, AGUIThinkingTextMessageEndEvent, AGUIThinkingTextMessageStartEvent, AGUIToolCall, AGUIToolCallArgsEvent, AGUIToolCallChunkEvent, AGUIToolCallEndEvent, AGUIToolCallResultEvent, AGUIToolCallStartEvent, AGUIToolDefinition };
|
|
296
|
+
export { AGUIActivityDeltaEvent, AGUIActivitySnapshotEvent, AGUIBaseEvent, AGUICustomEvent, AGUIEvent, AGUIEventType, AGUIFixture, AGUIFixtureMatch, AGUIInterrupt, AGUIMessage, AGUIMessageContentPart, AGUIMessageRole, AGUIMessagesSnapshotEvent, AGUIMockOptions, AGUIRawEvent, AGUIReasoningEncryptedValueEvent, AGUIReasoningEncryptedValueSubtype, AGUIReasoningEndEvent, AGUIReasoningMessageChunkEvent, AGUIReasoningMessageContentEvent, AGUIReasoningMessageEndEvent, AGUIReasoningMessageStartEvent, AGUIReasoningStartEvent, AGUIRecordConfig, AGUIResumeEntry, AGUIRunAgentInput, AGUIRunErrorEvent, AGUIRunFinishedEvent, AGUIRunFinishedOutcome, AGUIRunStartedEvent, AGUIStateDeltaEvent, AGUIStateSnapshotEvent, AGUIStepFinishedEvent, AGUIStepStartedEvent, AGUITextMessageChunkEvent, AGUITextMessageContentEvent, AGUITextMessageEndEvent, AGUITextMessageRole, AGUITextMessageStartEvent, AGUIThinkingEndEvent, AGUIThinkingStartEvent, AGUIThinkingTextMessageContentEvent, AGUIThinkingTextMessageEndEvent, AGUIThinkingTextMessageStartEvent, AGUITokenUsage, AGUIToolCall, AGUIToolCallArgsEvent, AGUIToolCallChunkEvent, AGUIToolCallEndEvent, AGUIToolCallResultEvent, AGUIToolCallStartEvent, AGUIToolDefinition };
|
|
279
297
|
//# sourceMappingURL=agui-types.d.cts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agui-types.d.cts","names":[],"sources":["../src/agui-types.ts"],"sourcesContent":[],"mappings":";KAOY,aAAA;AAAA,UA6CK,aAAA,CA7CQ;EA6CR,IAAA,EACT,aADsB;EAUb,SAAA,CAAA,EAAA,MAAA;EAAoB,QAAA,CAAA,EAAA,OAAA;;AAAQ,UAA5B,mBAAA,SAA4B,aAAA,CAAA;EAAa,IAAA,EAAA,aAAA;EAQzC,QAAA,EAAA,MAAA;EAAqB,KAAA,EAAA,MAAA;aAK1B,CAAA,EAAA,MAAA;
|
|
1
|
+
{"version":3,"file":"agui-types.d.cts","names":[],"sources":["../src/agui-types.ts"],"sourcesContent":[],"mappings":"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"}
|
package/dist/agui-types.d.ts
CHANGED
|
@@ -18,11 +18,13 @@ interface AGUIRunFinishedEvent extends AGUIBaseEvent {
|
|
|
18
18
|
runId: string;
|
|
19
19
|
result?: unknown;
|
|
20
20
|
outcome?: AGUIRunFinishedOutcome;
|
|
21
|
+
usage?: AGUITokenUsage[];
|
|
21
22
|
}
|
|
22
23
|
interface AGUIRunErrorEvent extends AGUIBaseEvent {
|
|
23
24
|
type: "RUN_ERROR";
|
|
24
25
|
message: string;
|
|
25
26
|
code?: string;
|
|
27
|
+
usage?: AGUITokenUsage[];
|
|
26
28
|
}
|
|
27
29
|
interface AGUIStepStartedEvent extends AGUIBaseEvent {
|
|
28
30
|
type: "STEP_STARTED";
|
|
@@ -192,6 +194,22 @@ type AGUIRunFinishedOutcome = {
|
|
|
192
194
|
type: "interrupt";
|
|
193
195
|
interrupts: AGUIInterrupt[];
|
|
194
196
|
};
|
|
197
|
+
/**
|
|
198
|
+
* Numeric-only token usage summary, mirroring `TokenUsageSchema` in
|
|
199
|
+
* `@ag-ui/core`. Deliberately carries no content-bearing or identifying
|
|
200
|
+
* fields — only provider/model labels and non-negative integer token counts.
|
|
201
|
+
* Carried as an array on RUN_FINISHED / RUN_ERROR so a run that invokes
|
|
202
|
+
* multiple models keeps them separate.
|
|
203
|
+
*/
|
|
204
|
+
interface AGUITokenUsage {
|
|
205
|
+
provider?: string;
|
|
206
|
+
model?: string;
|
|
207
|
+
inputTokens?: number;
|
|
208
|
+
outputTokens?: number;
|
|
209
|
+
totalTokens?: number;
|
|
210
|
+
reasoningTokens?: number;
|
|
211
|
+
cachedInputTokens?: number;
|
|
212
|
+
}
|
|
195
213
|
interface AGUIRunAgentInput {
|
|
196
214
|
threadId: string;
|
|
197
215
|
runId: string;
|
|
@@ -275,5 +293,5 @@ interface AGUIRecordConfig {
|
|
|
275
293
|
}
|
|
276
294
|
//# sourceMappingURL=agui-types.d.ts.map
|
|
277
295
|
//#endregion
|
|
278
|
-
export { AGUIActivityDeltaEvent, AGUIActivitySnapshotEvent, AGUIBaseEvent, AGUICustomEvent, AGUIEvent, AGUIEventType, AGUIFixture, AGUIFixtureMatch, AGUIInterrupt, AGUIMessage, AGUIMessageContentPart, AGUIMessageRole, AGUIMessagesSnapshotEvent, AGUIMockOptions, AGUIRawEvent, AGUIReasoningEncryptedValueEvent, AGUIReasoningEncryptedValueSubtype, AGUIReasoningEndEvent, AGUIReasoningMessageChunkEvent, AGUIReasoningMessageContentEvent, AGUIReasoningMessageEndEvent, AGUIReasoningMessageStartEvent, AGUIReasoningStartEvent, AGUIRecordConfig, AGUIResumeEntry, AGUIRunAgentInput, AGUIRunErrorEvent, AGUIRunFinishedEvent, AGUIRunFinishedOutcome, AGUIRunStartedEvent, AGUIStateDeltaEvent, AGUIStateSnapshotEvent, AGUIStepFinishedEvent, AGUIStepStartedEvent, AGUITextMessageChunkEvent, AGUITextMessageContentEvent, AGUITextMessageEndEvent, AGUITextMessageRole, AGUITextMessageStartEvent, AGUIThinkingEndEvent, AGUIThinkingStartEvent, AGUIThinkingTextMessageContentEvent, AGUIThinkingTextMessageEndEvent, AGUIThinkingTextMessageStartEvent, AGUIToolCall, AGUIToolCallArgsEvent, AGUIToolCallChunkEvent, AGUIToolCallEndEvent, AGUIToolCallResultEvent, AGUIToolCallStartEvent, AGUIToolDefinition };
|
|
296
|
+
export { AGUIActivityDeltaEvent, AGUIActivitySnapshotEvent, AGUIBaseEvent, AGUICustomEvent, AGUIEvent, AGUIEventType, AGUIFixture, AGUIFixtureMatch, AGUIInterrupt, AGUIMessage, AGUIMessageContentPart, AGUIMessageRole, AGUIMessagesSnapshotEvent, AGUIMockOptions, AGUIRawEvent, AGUIReasoningEncryptedValueEvent, AGUIReasoningEncryptedValueSubtype, AGUIReasoningEndEvent, AGUIReasoningMessageChunkEvent, AGUIReasoningMessageContentEvent, AGUIReasoningMessageEndEvent, AGUIReasoningMessageStartEvent, AGUIReasoningStartEvent, AGUIRecordConfig, AGUIResumeEntry, AGUIRunAgentInput, AGUIRunErrorEvent, AGUIRunFinishedEvent, AGUIRunFinishedOutcome, AGUIRunStartedEvent, AGUIStateDeltaEvent, AGUIStateSnapshotEvent, AGUIStepFinishedEvent, AGUIStepStartedEvent, AGUITextMessageChunkEvent, AGUITextMessageContentEvent, AGUITextMessageEndEvent, AGUITextMessageRole, AGUITextMessageStartEvent, AGUIThinkingEndEvent, AGUIThinkingStartEvent, AGUIThinkingTextMessageContentEvent, AGUIThinkingTextMessageEndEvent, AGUIThinkingTextMessageStartEvent, AGUITokenUsage, AGUIToolCall, AGUIToolCallArgsEvent, AGUIToolCallChunkEvent, AGUIToolCallEndEvent, AGUIToolCallResultEvent, AGUIToolCallStartEvent, AGUIToolDefinition };
|
|
279
297
|
//# sourceMappingURL=agui-types.d.ts.map
|
package/dist/agui-types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agui-types.d.ts","names":[],"sources":["../src/agui-types.ts"],"sourcesContent":[],"mappings":";KAOY,aAAA;AAAA,UA6CK,aAAA,CA7CQ;EA6CR,IAAA,EACT,aADsB;EAUb,SAAA,CAAA,EAAA,MAAA;EAAoB,QAAA,CAAA,EAAA,OAAA;;AAAQ,UAA5B,mBAAA,SAA4B,aAAA,CAAA;EAAa,IAAA,EAAA,aAAA;EAQzC,QAAA,EAAA,MAAA;EAAqB,KAAA,EAAA,MAAA;aAK1B,CAAA,EAAA,MAAA;
|
|
1
|
+
{"version":3,"file":"agui-types.d.ts","names":[],"sources":["../src/agui-types.ts"],"sourcesContent":[],"mappings":";KAOY,aAAA;AAAA,UA6CK,aAAA,CA7CQ;EA6CR,IAAA,EACT,aADsB;EAUb,SAAA,CAAA,EAAA,MAAA;EAAoB,QAAA,CAAA,EAAA,OAAA;;AAAQ,UAA5B,mBAAA,SAA4B,aAAA,CAAA;EAAa,IAAA,EAAA,aAAA;EAQzC,QAAA,EAAA,MAAA;EAAqB,KAAA,EAAA,MAAA;aAK1B,CAAA,EAAA,MAAA;OACF,CAAA,EATA,iBASA;;AANiD,UAA1C,oBAAA,SAA6B,aAAa,CAAA;EAS1C,IAAA,EAAA,cAAkB;EAAA,QAAA,EAAA,MAAA;OAIzB,EAAA,MAAA;QAJiC,CAAA,EAAA,OAAA;EAAa,OAAA,CAAA,EAJ5C,sBAI4C;EAOvC,KAAA,CAAA,EAVP,cAUO,EAAqB;AAKtC;AAOY,UAnBK,iBAAA,SAA0B,aAmBZ,CAAA;EAEnB,IAAA,EAAA,WAAA;EASK,OAAA,EAAA,MAAA;EAA0B,IAAA,CAAA,EAAA,MAAA;OAGnC,CAAA,EA7BE,cA6BF,EAAA;;AAHwD,UAvB/C,oBAAA,SAA6B,aAuBkB,CAAA;EAO/C,IAAA,EAAA,cAAA;EAMA,QAAA,EAAA,MAAA;AAKjB;AAA2C,UApC1B,qBAAA,SAA8B,aAoCJ,CAAA;MAGlC,EAAA,eAAA;UAH0C,EAAA,MAAA;;AAUlC,KAvCL,mBAAA,GAuC4B,WAAQ,GAAA,QAAa,GAAA,WAAA,GAAA,MAAA;AAO5C,KA5CL,eAAA,GA4C2B,WAAQ,GAAA,QAAA,GAAa,WAAA,GAAA,MAAA,GAAA,MAAA,GAAA,UAAA,GAAA,WAAA;AAM3C,UAzCA,yBAAA,SAAkC,aAyCQ,CAAA;EAK1C,IAAA,EAAA,oBAAuB;EAQvB,SAAA,EAAA,MAAA;EAUA,IAAA,EA7DT,mBA6DgC;EAKvB,IAAA,CAAA,EAAA,MAAA;AAKjB;AAA2C,UAnE1B,2BAAA,SAAoC,aAmEV,CAAA;MAE/B,EAAA,sBAAA;WAFuC,EAAA,MAAA;EAAa,KAAA,EAAA,MAAA;AAOhE;AAA2C,UApE1B,uBAAA,SAAgC,aAoEN,CAAA;MAIhC,EAAA,kBAAA;WAJwC,EAAA,MAAA;;AAQlC,UAvEA,yBAAA,SAAkC,aAuEU,CAAA;EAS5C,IAAA,EAAA,oBAAwB;EAKxB,SAAA,CAAA,EAAA,MAAA;EAMA,IAAA,CAAA,EAxFR,mBAwFQ;EAMA,KAAA,CAAA,EAAA,MAAA;EAKA,IAAA,CAAA,EAAA,MAAA;AAMjB;AAKY,UAvGK,sBAAA,SAA+B,aAuGF,CAAA;EAE7B,IAAA,EAAA,iBAAA;EAAiC,UAAA,EAAA,MAAA;cAEvC,EAAA,MAAA;iBAF+C,CAAA,EAAA,MAAA;;AASzC,UA3GA,qBAAA,SAA8B,aA2GI,CAAA;EAMlC,IAAA,EAAA,gBAAgB;EAQhB,UAAA,EAAA,MAAA;EAKA,KAAA,EAAA,MAAA;AAIjB;AAIiB,UAhIA,oBAAA,SAA6B,aAgIe,CAAA;EAK5C,IAAA,EAAA,eAAA;EAML,UAAA,EAAS,MAAA;;AACjB,UAvIa,sBAAA,SAA+B,aAuI5C,CAAA;MACA,EAAA,iBAAA;YACA,CAAA,EAAA,MAAA;cACA,CAAA,EAAA,MAAA;iBACA,CAAA,EAAA,MAAA;OACA,CAAA,EAAA,MAAA;;AAEA,UAtIa,uBAAA,SAAgC,aAsI7C,CAAA;MACA,EAAA,kBAAA;WACA,EAAA,MAAA;YACA,EAAA,MAAA;SACA,EAAA,MAAA;MACA,CAAA,EAAA,MAAA;;AAEA,UAnIa,sBAAA,SAA+B,aAmI5C,CAAA;MACA,EAAA,gBAAA;UACA,EAAA,OAAA;;AAEA,UAlIa,mBAAA,SAA4B,aAkIzC,CAAA;MACA,EAAA,aAAA;OACA,EAAA,OAAA,EAAA;;AAEA,UAjIa,yBAAA,SAAkC,aAiI/C,CAAA;MACA,EAAA,mBAAA;UACA,EAjIQ,WAiIR,EAAA;;AAEA,UA9Ha,yBAAA,SAAkC,aA8H/C,CAAA;MACA,EAAA,mBAAA;WACA,EAAA,MAAA;cACA,EAAA,MAAA;SACA,EA9HO,MA8HP,CAAA,MAAA,EAAA,OAAA,CAAA;SACA,CAAA,EAAA,OAAA;;AAC+B,UA5HlB,sBAAA,SAA+B,aA4Hb,CAAA;EAIlB,IAAA,EAAA,gBAAa;EAAA,SAAA,EAAA,MAAA;cAKX,EAAA,MAAA;OAEN,EAAA,OAAA,EAAA;;AAGI,UAjIA,uBAAA,SAAgC,aAiIjB,CAAA;EAMpB,IAAA,EAAA,iBAAA;EAWK,SAAA,EAAA,MAAc;AAY/B;AAAkC,UAzJjB,8BAAA,SAAuC,aAyJtB,CAAA;MAKrB,EAAA,yBAAA;WACH,EAAA,MAAA;MACE,EAAA,WAAA;;AAEc,UA5JT,gCAAA,SAAyC,aA4JhC,CAAA;EAGT,IAAA,EAAA,2BAAY;EAOjB,SAAA,EAAA,MAAA;EAIK,KAAA,EAAA,MAAA;;AAET,UAtKS,4BAAA,SAAqC,aAsK9C,CAAA;MACa,EAAA,uBAAA;WAKP,EAAA,MAAA;;AAGG,UA1KA,8BAAA,SAAuC,aA8KrC,CAAA;EAKF,IAAA,EAAA,yBAAgB;EAAA,SAAA,CAAA,EAAA,MAAA;OACZ,CAAA,EAAA,MAAA;;AAIkB,UAlLtB,qBAAA,SAA8B,aAkLR,CAAA;EAGtB,IAAA,EAAA,eAAW;EAAA,SAAA,EAAA,MAAA;;AAElB,KAlLE,kCAAA,GAkLF,WAAA,GAAA,SAAA;AAAS,UAhLF,gCAAA,SAAyC,aAgLvC,CAAA;EAIF,IAAA,EAAA,2BAAe;EAMf,OAAA,EAxLN,kCAwLsB;;;;UAjLhB,YAAA,SAAqB;;;;;UAMrB,eAAA,SAAwB;;;;;UAQxB,sBAAA,SAA+B;;;;UAK/B,oBAAA,SAA6B;;;UAI7B,iCAAA,SAA0C;;;UAI1C,mCAAA,SAA4C;;;;UAK5C,+BAAA,SAAwC;;;KAM7C,SAAA,GACR,sBACA,uBACA,oBACA,uBACA,wBACA,4BACA,8BACA,0BACA,4BACA,yBACA,wBACA,uBACA,yBACA,0BACA,yBACA,sBACA,4BACA,4BACA,yBACA,0BACA,iCACA,mCACA,+BACA,iCACA,wBACA,mCACA,eACA,kBACA,yBACA,uBACA,oCACA,sCACA;UAIa,aAAA;;;;;mBAKE;;aAEN;;UAGI,eAAA;;;;;KAML,sBAAA;;;;cAEyB;;;;;;;;;UASpB,cAAA;;;;;;;;;UAYA,iBAAA;;;;;aAKJ;UACH;YACE;;;;;WAED;;UAGM,YAAA;;;;;;;;;KAOL,sBAAA;;;;;;;UAIK,WAAA;;QAET;qBACa;;;;;cAKP;;UAGG,kBAAA;;;;aAIJ;;UAKI,gBAAA;qBACI;;;;sBAIC;;UAGL,WAAA;SACR;UACC;;;UAIO,eAAA;;;;;UAMA,gBAAA"}
|
package/dist/aimock-cli.cjs
CHANGED
|
@@ -99,7 +99,6 @@ function runAimockCli(deps = {}) {
|
|
|
99
99
|
port,
|
|
100
100
|
host
|
|
101
101
|
});
|
|
102
|
-
log(`aimock server listening on ${url}`);
|
|
103
102
|
function shutdown() {
|
|
104
103
|
log("Shutting down...");
|
|
105
104
|
process.removeListener("SIGINT", shutdown);
|
|
@@ -111,6 +110,7 @@ function runAimockCli(deps = {}) {
|
|
|
111
110
|
}
|
|
112
111
|
process.on("SIGINT", shutdown);
|
|
113
112
|
process.on("SIGTERM", shutdown);
|
|
113
|
+
log(`aimock server listening on ${url}`);
|
|
114
114
|
if (deps.onReady) deps.onReady({ shutdown });
|
|
115
115
|
}
|
|
116
116
|
main().catch((err) => {
|
package/dist/aimock-cli.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"aimock-cli.cjs","names":["loadConfig","startFromConfig"],"sources":["../src/aimock-cli.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { parseArgs } from \"node:util\";\nimport { resolve, basename } from \"node:path\";\nimport { loadConfig, startFromConfig } from \"./config-loader.js\";\nimport { runConvertCli, type ConvertCliDeps } from \"./convert.js\";\n\nconst HELP = `\nUsage: aimock [options]\n aimock convert <format> <input> [output]\n\nOptions:\n -c, --config <path> Path to aimock config JSON file (required)\n -p, --port <number> Port override (default: from config or 0)\n -h, --host <string> Host override (default: from config or 127.0.0.1)\n --help Show this help message\n\nSubcommands:\n convert Convert third-party mock configs to aimock format\n Run \"aimock convert --help\" for details\n`.trim();\n\nexport interface AimockCliDeps {\n argv?: string[];\n log?: (msg: string) => void;\n logError?: (msg: string) => void;\n exit?: (code: number) => void;\n loadConfigFn?: typeof loadConfig;\n startFromConfigFn?: typeof startFromConfig;\n onReady?: (ctx: { shutdown: () => void }) => void;\n convertDeps?: Partial<ConvertCliDeps>;\n}\n\nexport function runAimockCli(deps: AimockCliDeps = {}): void {\n /* v8 ignore next 6 -- defaults used only when called from CLI entry point */\n const argv = deps.argv ?? process.argv.slice(2);\n const log = deps.log ?? console.log.bind(console);\n const logError = deps.logError ?? console.error.bind(console);\n const exit = deps.exit ?? process.exit.bind(process);\n const loadConfigFn = deps.loadConfigFn ?? loadConfig;\n const startFromConfigFn = deps.startFromConfigFn ?? startFromConfig;\n\n // Intercept \"convert\" subcommand before parseArgs (which uses strict mode)\n if (argv[0] === \"convert\") {\n runConvertCli({\n argv: argv.slice(1),\n log,\n logError,\n exit,\n ...deps.convertDeps,\n });\n return;\n }\n\n let values;\n try {\n ({ values } = parseArgs({\n args: argv,\n options: {\n config: { type: \"string\", short: \"c\" },\n port: { type: \"string\", short: \"p\" },\n host: { type: \"string\", short: \"h\" },\n help: { type: \"boolean\", default: false },\n },\n strict: true,\n }));\n } catch (err) {\n /* v8 ignore next -- parseArgs always throws Error subclasses */\n const msg = err instanceof Error ? err.message : String(err);\n logError(`Error: ${msg}\\n\\n${HELP}`);\n exit(1);\n return;\n }\n\n if (values.help) {\n log(HELP);\n exit(0);\n return;\n }\n if (!values.config) {\n logError(\"Error: --config is required.\\n\\n\" + HELP);\n exit(1);\n return;\n }\n\n const configPath = resolve(values.config);\n let config;\n try {\n config = loadConfigFn(configPath);\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n logError(`Failed to load config from ${configPath}: ${msg}`);\n exit(1);\n return;\n }\n\n const port = values.port ? Number(values.port) : undefined;\n if (\n port !== undefined &&\n (Number.isNaN(port) || !Number.isInteger(port) || port < 0 || port > 65535)\n ) {\n logError(`Error: invalid port \"${values.port}\".\\n\\n${HELP}`);\n exit(1);\n return;\n }\n const host = values.host;\n\n async function main() {\n const { llmock, url } = await startFromConfigFn(config!, { port, host });\n
|
|
1
|
+
{"version":3,"file":"aimock-cli.cjs","names":["loadConfig","startFromConfig"],"sources":["../src/aimock-cli.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { parseArgs } from \"node:util\";\nimport { resolve, basename } from \"node:path\";\nimport { loadConfig, startFromConfig } from \"./config-loader.js\";\nimport { runConvertCli, type ConvertCliDeps } from \"./convert.js\";\n\nconst HELP = `\nUsage: aimock [options]\n aimock convert <format> <input> [output]\n\nOptions:\n -c, --config <path> Path to aimock config JSON file (required)\n -p, --port <number> Port override (default: from config or 0)\n -h, --host <string> Host override (default: from config or 127.0.0.1)\n --help Show this help message\n\nSubcommands:\n convert Convert third-party mock configs to aimock format\n Run \"aimock convert --help\" for details\n`.trim();\n\nexport interface AimockCliDeps {\n argv?: string[];\n log?: (msg: string) => void;\n logError?: (msg: string) => void;\n exit?: (code: number) => void;\n loadConfigFn?: typeof loadConfig;\n startFromConfigFn?: typeof startFromConfig;\n onReady?: (ctx: { shutdown: () => void }) => void;\n convertDeps?: Partial<ConvertCliDeps>;\n}\n\nexport function runAimockCli(deps: AimockCliDeps = {}): void {\n /* v8 ignore next 6 -- defaults used only when called from CLI entry point */\n const argv = deps.argv ?? process.argv.slice(2);\n const log = deps.log ?? console.log.bind(console);\n const logError = deps.logError ?? console.error.bind(console);\n const exit = deps.exit ?? process.exit.bind(process);\n const loadConfigFn = deps.loadConfigFn ?? loadConfig;\n const startFromConfigFn = deps.startFromConfigFn ?? startFromConfig;\n\n // Intercept \"convert\" subcommand before parseArgs (which uses strict mode)\n if (argv[0] === \"convert\") {\n runConvertCli({\n argv: argv.slice(1),\n log,\n logError,\n exit,\n ...deps.convertDeps,\n });\n return;\n }\n\n let values;\n try {\n ({ values } = parseArgs({\n args: argv,\n options: {\n config: { type: \"string\", short: \"c\" },\n port: { type: \"string\", short: \"p\" },\n host: { type: \"string\", short: \"h\" },\n help: { type: \"boolean\", default: false },\n },\n strict: true,\n }));\n } catch (err) {\n /* v8 ignore next -- parseArgs always throws Error subclasses */\n const msg = err instanceof Error ? err.message : String(err);\n logError(`Error: ${msg}\\n\\n${HELP}`);\n exit(1);\n return;\n }\n\n if (values.help) {\n log(HELP);\n exit(0);\n return;\n }\n if (!values.config) {\n logError(\"Error: --config is required.\\n\\n\" + HELP);\n exit(1);\n return;\n }\n\n const configPath = resolve(values.config);\n let config;\n try {\n config = loadConfigFn(configPath);\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n logError(`Failed to load config from ${configPath}: ${msg}`);\n exit(1);\n return;\n }\n\n const port = values.port ? Number(values.port) : undefined;\n if (\n port !== undefined &&\n (Number.isNaN(port) || !Number.isInteger(port) || port < 0 || port > 65535)\n ) {\n logError(`Error: invalid port \"${values.port}\".\\n\\n${HELP}`);\n exit(1);\n return;\n }\n const host = values.host;\n\n async function main() {\n const { llmock, url } = await startFromConfigFn(config!, { port, host });\n\n function shutdown() {\n log(\"Shutting down...\");\n process.removeListener(\"SIGINT\", shutdown);\n process.removeListener(\"SIGTERM\", shutdown);\n llmock.stop().then(\n () => exit(0),\n (err) => {\n logError(`Shutdown error: ${err instanceof Error ? err.message : String(err)}`);\n exit(1);\n },\n );\n }\n // Register BEFORE announcing readiness — see the note in src/cli.ts. A supervisor\n // that reacts to the readiness line must never win a race against this listener.\n process.on(\"SIGINT\", shutdown);\n process.on(\"SIGTERM\", shutdown);\n\n log(`aimock server listening on ${url}`);\n\n if (deps.onReady) {\n deps.onReady({ shutdown });\n }\n }\n\n main().catch((err) => {\n logError(err instanceof Error ? err.message : String(err));\n exit(1);\n });\n}\n\n// Run when executed as a script (not when imported for testing).\n/* v8 ignore start -- entry-point guard, exercised by integration tests */\nconst scriptName = process.argv[1] ?? \"\";\nconst base = basename(scriptName);\nif (base === \"aimock\" || base === \"aimock-cli.js\" || base === \"aimock-cli.ts\") {\n runAimockCli();\n}\n/* v8 ignore stop */\n"],"mappings":";;;;;;;;;AAMA,MAAM,OAAO;;;;;;;;;;;;;EAaX,MAAM;AAaR,SAAgB,aAAa,OAAsB,EAAE,EAAQ;;CAE3D,MAAM,OAAO,KAAK,QAAQ,QAAQ,KAAK,MAAM,EAAE;CAC/C,MAAM,MAAM,KAAK,OAAO,QAAQ,IAAI,KAAK,QAAQ;CACjD,MAAM,WAAW,KAAK,YAAY,QAAQ,MAAM,KAAK,QAAQ;CAC7D,MAAM,OAAO,KAAK,QAAQ,QAAQ,KAAK,KAAK,QAAQ;CACpD,MAAM,eAAe,KAAK,gBAAgBA;CAC1C,MAAM,oBAAoB,KAAK,qBAAqBC;AAGpD,KAAI,KAAK,OAAO,WAAW;AACzB,gCAAc;GACZ,MAAM,KAAK,MAAM,EAAE;GACnB;GACA;GACA;GACA,GAAG,KAAK;GACT,CAAC;AACF;;CAGF,IAAI;AACJ,KAAI;AACF,GAAC,CAAE,mCAAqB;GACtB,MAAM;GACN,SAAS;IACP,QAAQ;KAAE,MAAM;KAAU,OAAO;KAAK;IACtC,MAAM;KAAE,MAAM;KAAU,OAAO;KAAK;IACpC,MAAM;KAAE,MAAM;KAAU,OAAO;KAAK;IACpC,MAAM;KAAE,MAAM;KAAW,SAAS;KAAO;IAC1C;GACD,QAAQ;GACT,CAAC;UACK,KAAK;AAGZ,WAAS,UADG,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI,CACrC,MAAM,OAAO;AACpC,OAAK,EAAE;AACP;;AAGF,KAAI,OAAO,MAAM;AACf,MAAI,KAAK;AACT,OAAK,EAAE;AACP;;AAEF,KAAI,CAAC,OAAO,QAAQ;AAClB,WAAS,qCAAqC,KAAK;AACnD,OAAK,EAAE;AACP;;CAGF,MAAM,oCAAqB,OAAO,OAAO;CACzC,IAAI;AACJ,KAAI;AACF,WAAS,aAAa,WAAW;UAC1B,KAAK;AAEZ,WAAS,8BAA8B,WAAW,IADtC,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI,GACA;AAC5D,OAAK,EAAE;AACP;;CAGF,MAAM,OAAO,OAAO,OAAO,OAAO,OAAO,KAAK,GAAG;AACjD,KACE,SAAS,WACR,OAAO,MAAM,KAAK,IAAI,CAAC,OAAO,UAAU,KAAK,IAAI,OAAO,KAAK,OAAO,QACrE;AACA,WAAS,wBAAwB,OAAO,KAAK,QAAQ,OAAO;AAC5D,OAAK,EAAE;AACP;;CAEF,MAAM,OAAO,OAAO;CAEpB,eAAe,OAAO;EACpB,MAAM,EAAE,QAAQ,QAAQ,MAAM,kBAAkB,QAAS;GAAE;GAAM;GAAM,CAAC;EAExE,SAAS,WAAW;AAClB,OAAI,mBAAmB;AACvB,WAAQ,eAAe,UAAU,SAAS;AAC1C,WAAQ,eAAe,WAAW,SAAS;AAC3C,UAAO,MAAM,CAAC,WACN,KAAK,EAAE,GACZ,QAAQ;AACP,aAAS,mBAAmB,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI,GAAG;AAC/E,SAAK,EAAE;KAEV;;AAIH,UAAQ,GAAG,UAAU,SAAS;AAC9B,UAAQ,GAAG,WAAW,SAAS;AAE/B,MAAI,8BAA8B,MAAM;AAExC,MAAI,KAAK,QACP,MAAK,QAAQ,EAAE,UAAU,CAAC;;AAI9B,OAAM,CAAC,OAAO,QAAQ;AACpB,WAAS,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI,CAAC;AAC1D,OAAK,EAAE;GACP;;AAMJ,MAAM,+BADa,QAAQ,KAAK,MAAM,GACL;AACjC,IAAI,SAAS,YAAY,SAAS,mBAAmB,SAAS,gBAC5D,eAAc"}
|
package/dist/aimock-cli.js
CHANGED
|
@@ -97,7 +97,6 @@ function runAimockCli(deps = {}) {
|
|
|
97
97
|
port,
|
|
98
98
|
host
|
|
99
99
|
});
|
|
100
|
-
log(`aimock server listening on ${url}`);
|
|
101
100
|
function shutdown() {
|
|
102
101
|
log("Shutting down...");
|
|
103
102
|
process.removeListener("SIGINT", shutdown);
|
|
@@ -109,6 +108,7 @@ function runAimockCli(deps = {}) {
|
|
|
109
108
|
}
|
|
110
109
|
process.on("SIGINT", shutdown);
|
|
111
110
|
process.on("SIGTERM", shutdown);
|
|
111
|
+
log(`aimock server listening on ${url}`);
|
|
112
112
|
if (deps.onReady) deps.onReady({ shutdown });
|
|
113
113
|
}
|
|
114
114
|
main().catch((err) => {
|
package/dist/aimock-cli.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"aimock-cli.js","names":[],"sources":["../src/aimock-cli.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { parseArgs } from \"node:util\";\nimport { resolve, basename } from \"node:path\";\nimport { loadConfig, startFromConfig } from \"./config-loader.js\";\nimport { runConvertCli, type ConvertCliDeps } from \"./convert.js\";\n\nconst HELP = `\nUsage: aimock [options]\n aimock convert <format> <input> [output]\n\nOptions:\n -c, --config <path> Path to aimock config JSON file (required)\n -p, --port <number> Port override (default: from config or 0)\n -h, --host <string> Host override (default: from config or 127.0.0.1)\n --help Show this help message\n\nSubcommands:\n convert Convert third-party mock configs to aimock format\n Run \"aimock convert --help\" for details\n`.trim();\n\nexport interface AimockCliDeps {\n argv?: string[];\n log?: (msg: string) => void;\n logError?: (msg: string) => void;\n exit?: (code: number) => void;\n loadConfigFn?: typeof loadConfig;\n startFromConfigFn?: typeof startFromConfig;\n onReady?: (ctx: { shutdown: () => void }) => void;\n convertDeps?: Partial<ConvertCliDeps>;\n}\n\nexport function runAimockCli(deps: AimockCliDeps = {}): void {\n /* v8 ignore next 6 -- defaults used only when called from CLI entry point */\n const argv = deps.argv ?? process.argv.slice(2);\n const log = deps.log ?? console.log.bind(console);\n const logError = deps.logError ?? console.error.bind(console);\n const exit = deps.exit ?? process.exit.bind(process);\n const loadConfigFn = deps.loadConfigFn ?? loadConfig;\n const startFromConfigFn = deps.startFromConfigFn ?? startFromConfig;\n\n // Intercept \"convert\" subcommand before parseArgs (which uses strict mode)\n if (argv[0] === \"convert\") {\n runConvertCli({\n argv: argv.slice(1),\n log,\n logError,\n exit,\n ...deps.convertDeps,\n });\n return;\n }\n\n let values;\n try {\n ({ values } = parseArgs({\n args: argv,\n options: {\n config: { type: \"string\", short: \"c\" },\n port: { type: \"string\", short: \"p\" },\n host: { type: \"string\", short: \"h\" },\n help: { type: \"boolean\", default: false },\n },\n strict: true,\n }));\n } catch (err) {\n /* v8 ignore next -- parseArgs always throws Error subclasses */\n const msg = err instanceof Error ? err.message : String(err);\n logError(`Error: ${msg}\\n\\n${HELP}`);\n exit(1);\n return;\n }\n\n if (values.help) {\n log(HELP);\n exit(0);\n return;\n }\n if (!values.config) {\n logError(\"Error: --config is required.\\n\\n\" + HELP);\n exit(1);\n return;\n }\n\n const configPath = resolve(values.config);\n let config;\n try {\n config = loadConfigFn(configPath);\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n logError(`Failed to load config from ${configPath}: ${msg}`);\n exit(1);\n return;\n }\n\n const port = values.port ? Number(values.port) : undefined;\n if (\n port !== undefined &&\n (Number.isNaN(port) || !Number.isInteger(port) || port < 0 || port > 65535)\n ) {\n logError(`Error: invalid port \"${values.port}\".\\n\\n${HELP}`);\n exit(1);\n return;\n }\n const host = values.host;\n\n async function main() {\n const { llmock, url } = await startFromConfigFn(config!, { port, host });\n
|
|
1
|
+
{"version":3,"file":"aimock-cli.js","names":[],"sources":["../src/aimock-cli.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { parseArgs } from \"node:util\";\nimport { resolve, basename } from \"node:path\";\nimport { loadConfig, startFromConfig } from \"./config-loader.js\";\nimport { runConvertCli, type ConvertCliDeps } from \"./convert.js\";\n\nconst HELP = `\nUsage: aimock [options]\n aimock convert <format> <input> [output]\n\nOptions:\n -c, --config <path> Path to aimock config JSON file (required)\n -p, --port <number> Port override (default: from config or 0)\n -h, --host <string> Host override (default: from config or 127.0.0.1)\n --help Show this help message\n\nSubcommands:\n convert Convert third-party mock configs to aimock format\n Run \"aimock convert --help\" for details\n`.trim();\n\nexport interface AimockCliDeps {\n argv?: string[];\n log?: (msg: string) => void;\n logError?: (msg: string) => void;\n exit?: (code: number) => void;\n loadConfigFn?: typeof loadConfig;\n startFromConfigFn?: typeof startFromConfig;\n onReady?: (ctx: { shutdown: () => void }) => void;\n convertDeps?: Partial<ConvertCliDeps>;\n}\n\nexport function runAimockCli(deps: AimockCliDeps = {}): void {\n /* v8 ignore next 6 -- defaults used only when called from CLI entry point */\n const argv = deps.argv ?? process.argv.slice(2);\n const log = deps.log ?? console.log.bind(console);\n const logError = deps.logError ?? console.error.bind(console);\n const exit = deps.exit ?? process.exit.bind(process);\n const loadConfigFn = deps.loadConfigFn ?? loadConfig;\n const startFromConfigFn = deps.startFromConfigFn ?? startFromConfig;\n\n // Intercept \"convert\" subcommand before parseArgs (which uses strict mode)\n if (argv[0] === \"convert\") {\n runConvertCli({\n argv: argv.slice(1),\n log,\n logError,\n exit,\n ...deps.convertDeps,\n });\n return;\n }\n\n let values;\n try {\n ({ values } = parseArgs({\n args: argv,\n options: {\n config: { type: \"string\", short: \"c\" },\n port: { type: \"string\", short: \"p\" },\n host: { type: \"string\", short: \"h\" },\n help: { type: \"boolean\", default: false },\n },\n strict: true,\n }));\n } catch (err) {\n /* v8 ignore next -- parseArgs always throws Error subclasses */\n const msg = err instanceof Error ? err.message : String(err);\n logError(`Error: ${msg}\\n\\n${HELP}`);\n exit(1);\n return;\n }\n\n if (values.help) {\n log(HELP);\n exit(0);\n return;\n }\n if (!values.config) {\n logError(\"Error: --config is required.\\n\\n\" + HELP);\n exit(1);\n return;\n }\n\n const configPath = resolve(values.config);\n let config;\n try {\n config = loadConfigFn(configPath);\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n logError(`Failed to load config from ${configPath}: ${msg}`);\n exit(1);\n return;\n }\n\n const port = values.port ? Number(values.port) : undefined;\n if (\n port !== undefined &&\n (Number.isNaN(port) || !Number.isInteger(port) || port < 0 || port > 65535)\n ) {\n logError(`Error: invalid port \"${values.port}\".\\n\\n${HELP}`);\n exit(1);\n return;\n }\n const host = values.host;\n\n async function main() {\n const { llmock, url } = await startFromConfigFn(config!, { port, host });\n\n function shutdown() {\n log(\"Shutting down...\");\n process.removeListener(\"SIGINT\", shutdown);\n process.removeListener(\"SIGTERM\", shutdown);\n llmock.stop().then(\n () => exit(0),\n (err) => {\n logError(`Shutdown error: ${err instanceof Error ? err.message : String(err)}`);\n exit(1);\n },\n );\n }\n // Register BEFORE announcing readiness — see the note in src/cli.ts. A supervisor\n // that reacts to the readiness line must never win a race against this listener.\n process.on(\"SIGINT\", shutdown);\n process.on(\"SIGTERM\", shutdown);\n\n log(`aimock server listening on ${url}`);\n\n if (deps.onReady) {\n deps.onReady({ shutdown });\n }\n }\n\n main().catch((err) => {\n logError(err instanceof Error ? err.message : String(err));\n exit(1);\n });\n}\n\n// Run when executed as a script (not when imported for testing).\n/* v8 ignore start -- entry-point guard, exercised by integration tests */\nconst scriptName = process.argv[1] ?? \"\";\nconst base = basename(scriptName);\nif (base === \"aimock\" || base === \"aimock-cli.js\" || base === \"aimock-cli.ts\") {\n runAimockCli();\n}\n/* v8 ignore stop */\n"],"mappings":";;;;;;;AAMA,MAAM,OAAO;;;;;;;;;;;;;EAaX,MAAM;AAaR,SAAgB,aAAa,OAAsB,EAAE,EAAQ;;CAE3D,MAAM,OAAO,KAAK,QAAQ,QAAQ,KAAK,MAAM,EAAE;CAC/C,MAAM,MAAM,KAAK,OAAO,QAAQ,IAAI,KAAK,QAAQ;CACjD,MAAM,WAAW,KAAK,YAAY,QAAQ,MAAM,KAAK,QAAQ;CAC7D,MAAM,OAAO,KAAK,QAAQ,QAAQ,KAAK,KAAK,QAAQ;CACpD,MAAM,eAAe,KAAK,gBAAgB;CAC1C,MAAM,oBAAoB,KAAK,qBAAqB;AAGpD,KAAI,KAAK,OAAO,WAAW;AACzB,gBAAc;GACZ,MAAM,KAAK,MAAM,EAAE;GACnB;GACA;GACA;GACA,GAAG,KAAK;GACT,CAAC;AACF;;CAGF,IAAI;AACJ,KAAI;AACF,GAAC,CAAE,UAAW,UAAU;GACtB,MAAM;GACN,SAAS;IACP,QAAQ;KAAE,MAAM;KAAU,OAAO;KAAK;IACtC,MAAM;KAAE,MAAM;KAAU,OAAO;KAAK;IACpC,MAAM;KAAE,MAAM;KAAU,OAAO;KAAK;IACpC,MAAM;KAAE,MAAM;KAAW,SAAS;KAAO;IAC1C;GACD,QAAQ;GACT,CAAC;UACK,KAAK;AAGZ,WAAS,UADG,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI,CACrC,MAAM,OAAO;AACpC,OAAK,EAAE;AACP;;AAGF,KAAI,OAAO,MAAM;AACf,MAAI,KAAK;AACT,OAAK,EAAE;AACP;;AAEF,KAAI,CAAC,OAAO,QAAQ;AAClB,WAAS,qCAAqC,KAAK;AACnD,OAAK,EAAE;AACP;;CAGF,MAAM,aAAa,QAAQ,OAAO,OAAO;CACzC,IAAI;AACJ,KAAI;AACF,WAAS,aAAa,WAAW;UAC1B,KAAK;AAEZ,WAAS,8BAA8B,WAAW,IADtC,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI,GACA;AAC5D,OAAK,EAAE;AACP;;CAGF,MAAM,OAAO,OAAO,OAAO,OAAO,OAAO,KAAK,GAAG;AACjD,KACE,SAAS,WACR,OAAO,MAAM,KAAK,IAAI,CAAC,OAAO,UAAU,KAAK,IAAI,OAAO,KAAK,OAAO,QACrE;AACA,WAAS,wBAAwB,OAAO,KAAK,QAAQ,OAAO;AAC5D,OAAK,EAAE;AACP;;CAEF,MAAM,OAAO,OAAO;CAEpB,eAAe,OAAO;EACpB,MAAM,EAAE,QAAQ,QAAQ,MAAM,kBAAkB,QAAS;GAAE;GAAM;GAAM,CAAC;EAExE,SAAS,WAAW;AAClB,OAAI,mBAAmB;AACvB,WAAQ,eAAe,UAAU,SAAS;AAC1C,WAAQ,eAAe,WAAW,SAAS;AAC3C,UAAO,MAAM,CAAC,WACN,KAAK,EAAE,GACZ,QAAQ;AACP,aAAS,mBAAmB,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI,GAAG;AAC/E,SAAK,EAAE;KAEV;;AAIH,UAAQ,GAAG,UAAU,SAAS;AAC9B,UAAQ,GAAG,WAAW,SAAS;AAE/B,MAAI,8BAA8B,MAAM;AAExC,MAAI,KAAK,QACP,MAAK,QAAQ,EAAE,UAAU,CAAC;;AAI9B,OAAM,CAAC,OAAO,QAAQ;AACpB,WAAS,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI,CAAC;AAC1D,OAAK,EAAE;GACP;;AAMJ,MAAM,OAAO,SADM,QAAQ,KAAK,MAAM,GACL;AACjC,IAAI,SAAS,YAAY,SAAS,mBAAmB,SAAS,gBAC5D,eAAc"}
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
const require_runtime = require('./_virtual/_rolldown/runtime.cjs');
|
|
2
|
+
let node_crypto = require("node:crypto");
|
|
3
|
+
|
|
4
|
+
//#region src/api-key-auth.ts
|
|
5
|
+
const INBOUND_API_KEY_HEADERS = [
|
|
6
|
+
"authorization",
|
|
7
|
+
"x-api-key",
|
|
8
|
+
"x-goog-api-key",
|
|
9
|
+
"api-key",
|
|
10
|
+
"xi-api-key"
|
|
11
|
+
];
|
|
12
|
+
const RECOGNIZED_HEADERS = new Set(INBOUND_API_KEY_HEADERS);
|
|
13
|
+
const API_KEY_ERROR_BODY = JSON.stringify({ error: {
|
|
14
|
+
message: "Invalid API key",
|
|
15
|
+
type: "authentication_error",
|
|
16
|
+
code: "invalid_api_key"
|
|
17
|
+
} });
|
|
18
|
+
const DISABLED_POLICY = {
|
|
19
|
+
enabled: false,
|
|
20
|
+
keys: []
|
|
21
|
+
};
|
|
22
|
+
const authenticatedRequests = /* @__PURE__ */ new WeakSet();
|
|
23
|
+
const OWS_EDGES = /^[ \t]+|[ \t]+$/g;
|
|
24
|
+
function fail(label, suffix) {
|
|
25
|
+
throw new Error(`${label}${suffix}`);
|
|
26
|
+
}
|
|
27
|
+
function hasControl(value) {
|
|
28
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
29
|
+
const code = value.charCodeAt(index);
|
|
30
|
+
if (code <= 31 || code === 127) return true;
|
|
31
|
+
}
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
function normalizeKey(value, label) {
|
|
35
|
+
if (typeof value !== "string") fail(label, " must be a string");
|
|
36
|
+
const normalized = value.replace(OWS_EDGES, "");
|
|
37
|
+
if (normalized.length === 0) fail(label, " must not be empty");
|
|
38
|
+
if (hasControl(normalized)) fail(label, " must not contain control characters");
|
|
39
|
+
return normalized;
|
|
40
|
+
}
|
|
41
|
+
/** Parse a public auth source once and construct its private server policy. */
|
|
42
|
+
function resolveInboundAuth(source) {
|
|
43
|
+
if (source.value === void 0) return { policy: DISABLED_POLICY };
|
|
44
|
+
if (source.value === null || typeof source.value !== "object" || Array.isArray(source.value)) fail(source.label, " must be an object");
|
|
45
|
+
const apiKeys = source.value.apiKeys;
|
|
46
|
+
if (!Array.isArray(apiKeys)) fail(`${source.label}.apiKeys`, " must be an array");
|
|
47
|
+
if (apiKeys.length === 0) fail(`${source.label}.apiKeys`, " must not be empty");
|
|
48
|
+
const keys = apiKeys.map((value, index) => normalizeKey(value, `${source.label}.apiKeys[${index}]`));
|
|
49
|
+
const seen = /* @__PURE__ */ new Set();
|
|
50
|
+
for (const key of keys) {
|
|
51
|
+
if (seen.has(key)) fail(source.label, " must not contain duplicate API keys");
|
|
52
|
+
seen.add(key);
|
|
53
|
+
}
|
|
54
|
+
return {
|
|
55
|
+
publicConfig: { apiKeys: keys },
|
|
56
|
+
policy: {
|
|
57
|
+
enabled: true,
|
|
58
|
+
keys: keys.map((key) => Buffer.from(key, "utf8"))
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
/** Select configuration without parsing it, so an environment override bypasses malformed JSON auth. */
|
|
63
|
+
function selectInboundAuthSource(configAuth, env = process.env) {
|
|
64
|
+
if (Object.prototype.hasOwnProperty.call(env, "AIMOCK_API_KEYS")) return {
|
|
65
|
+
value: { apiKeys: (env.AIMOCK_API_KEYS ?? "").split(",") },
|
|
66
|
+
label: "AIMOCK_API_KEYS"
|
|
67
|
+
};
|
|
68
|
+
return {
|
|
69
|
+
value: configAuth,
|
|
70
|
+
label: "aimock.json.auth"
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
function isRecognizedApiKeyHeader(name) {
|
|
74
|
+
return RECOGNIZED_HEADERS.has(name.toLowerCase());
|
|
75
|
+
}
|
|
76
|
+
function rawCandidates(req) {
|
|
77
|
+
const candidates = [];
|
|
78
|
+
for (let index = 0; index < req.rawHeaders.length; index += 2) {
|
|
79
|
+
const name = req.rawHeaders[index];
|
|
80
|
+
const value = req.rawHeaders[index + 1] ?? "";
|
|
81
|
+
const lower = name.toLowerCase();
|
|
82
|
+
if (!isRecognizedApiKeyHeader(lower)) continue;
|
|
83
|
+
if (hasControl(value)) return { reason: "invalid" };
|
|
84
|
+
if (lower === "authorization") {
|
|
85
|
+
const match = /^(?:Bearer|Key)[ \t]+(.+)$/i.exec(value.replace(OWS_EDGES, ""));
|
|
86
|
+
if (!match) return { reason: "invalid" };
|
|
87
|
+
const candidate = match[1].replace(OWS_EDGES, "");
|
|
88
|
+
if (candidate.length === 0 || hasControl(candidate)) return { reason: "invalid" };
|
|
89
|
+
candidates.push(candidate);
|
|
90
|
+
} else {
|
|
91
|
+
const candidate = value.replace(OWS_EDGES, "");
|
|
92
|
+
if (candidate.length === 0) return { reason: "invalid" };
|
|
93
|
+
candidates.push(candidate);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return candidates.length > 0 ? { candidates } : { reason: "missing" };
|
|
97
|
+
}
|
|
98
|
+
function matchingIndex(candidate, keys) {
|
|
99
|
+
const encoded = Buffer.from(candidate, "utf8");
|
|
100
|
+
for (let index = 0; index < keys.length; index += 1) {
|
|
101
|
+
const key = keys[index];
|
|
102
|
+
if (encoded.length === key.length && (0, node_crypto.timingSafeEqual)(encoded, key)) return index;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
function validateRequestApiKey(req, policy) {
|
|
106
|
+
if (!policy.enabled) return { ok: true };
|
|
107
|
+
const extracted = rawCandidates(req);
|
|
108
|
+
if (!extracted.candidates) return {
|
|
109
|
+
ok: false,
|
|
110
|
+
reason: extracted.reason
|
|
111
|
+
};
|
|
112
|
+
let resolved;
|
|
113
|
+
for (const candidate of extracted.candidates) {
|
|
114
|
+
const index = matchingIndex(candidate, policy.keys);
|
|
115
|
+
if (index === void 0) return {
|
|
116
|
+
ok: false,
|
|
117
|
+
reason: "invalid"
|
|
118
|
+
};
|
|
119
|
+
if (resolved !== void 0 && resolved !== index) return {
|
|
120
|
+
ok: false,
|
|
121
|
+
reason: "conflict"
|
|
122
|
+
};
|
|
123
|
+
resolved = index;
|
|
124
|
+
}
|
|
125
|
+
return { ok: true };
|
|
126
|
+
}
|
|
127
|
+
/** @internal Opaque egress marker; it deliberately carries no key material. */
|
|
128
|
+
function markAuthenticatedRequest(req, policy) {
|
|
129
|
+
if (policy.enabled) authenticatedRequests.add(req);
|
|
130
|
+
}
|
|
131
|
+
/** @internal Used by egress paths to select safe credential handling. */
|
|
132
|
+
function isAuthenticatedRequest(req) {
|
|
133
|
+
return authenticatedRequests.has(req);
|
|
134
|
+
}
|
|
135
|
+
function writeApiKeyHttpRejection(res, setCorsHeaders) {
|
|
136
|
+
setCorsHeaders(res);
|
|
137
|
+
res.writeHead(401, {
|
|
138
|
+
"Content-Type": "application/json",
|
|
139
|
+
"Content-Length": Buffer.byteLength(API_KEY_ERROR_BODY),
|
|
140
|
+
"WWW-Authenticate": "Bearer realm=\"aimock\""
|
|
141
|
+
});
|
|
142
|
+
res.end(API_KEY_ERROR_BODY);
|
|
143
|
+
}
|
|
144
|
+
function writeApiKeyUpgradeRejection(socket) {
|
|
145
|
+
socket.write([
|
|
146
|
+
"HTTP/1.1 401 Unauthorized",
|
|
147
|
+
"Content-Type: application/json",
|
|
148
|
+
`Content-Length: ${Buffer.byteLength(API_KEY_ERROR_BODY)}`,
|
|
149
|
+
"WWW-Authenticate: Bearer realm=\"aimock\"",
|
|
150
|
+
"Connection: close",
|
|
151
|
+
"",
|
|
152
|
+
API_KEY_ERROR_BODY
|
|
153
|
+
].join("\r\n"));
|
|
154
|
+
socket.destroy();
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
//#endregion
|
|
158
|
+
exports.isAuthenticatedRequest = isAuthenticatedRequest;
|
|
159
|
+
exports.isRecognizedApiKeyHeader = isRecognizedApiKeyHeader;
|
|
160
|
+
exports.markAuthenticatedRequest = markAuthenticatedRequest;
|
|
161
|
+
exports.resolveInboundAuth = resolveInboundAuth;
|
|
162
|
+
exports.selectInboundAuthSource = selectInboundAuthSource;
|
|
163
|
+
exports.validateRequestApiKey = validateRequestApiKey;
|
|
164
|
+
exports.writeApiKeyHttpRejection = writeApiKeyHttpRejection;
|
|
165
|
+
exports.writeApiKeyUpgradeRejection = writeApiKeyUpgradeRejection;
|
|
166
|
+
//# sourceMappingURL=api-key-auth.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api-key-auth.cjs","names":[],"sources":["../src/api-key-auth.ts"],"sourcesContent":["import { timingSafeEqual } from \"node:crypto\";\nimport type * as http from \"node:http\";\nimport type * as net from \"node:net\";\nimport type { ApiKeyAuthConfig } from \"./types.js\";\n\nexport const INBOUND_API_KEY_HEADERS = [\n \"authorization\",\n \"x-api-key\",\n \"x-goog-api-key\",\n \"api-key\",\n \"xi-api-key\",\n] as const;\n\nconst RECOGNIZED_HEADERS = new Set<string>(INBOUND_API_KEY_HEADERS);\n\nexport const API_KEY_ERROR_BODY = JSON.stringify({\n error: {\n message: \"Invalid API key\",\n type: \"authentication_error\",\n code: \"invalid_api_key\",\n },\n});\n\n/** @internal */\nexport interface ApiKeyPolicy {\n readonly enabled: boolean;\n readonly keys: readonly Buffer[];\n}\n\n/** @internal */\nexport interface ResolvedInboundAuth {\n readonly publicConfig?: ApiKeyAuthConfig;\n readonly policy: ApiKeyPolicy;\n}\n\nexport interface AuthResult {\n readonly ok: boolean;\n readonly reason?: \"missing\" | \"invalid\" | \"conflict\";\n}\n\nconst DISABLED_POLICY: ApiKeyPolicy = { enabled: false, keys: [] };\nconst authenticatedRequests = new WeakSet<http.IncomingMessage>();\nconst OWS_EDGES = /^[ \\t]+|[ \\t]+$/g;\n\nfunction fail(label: string, suffix: string): never {\n throw new Error(`${label}${suffix}`);\n}\n\nfunction hasControl(value: string): boolean {\n for (let index = 0; index < value.length; index += 1) {\n const code = value.charCodeAt(index);\n if (code <= 0x1f || code === 0x7f) return true;\n }\n return false;\n}\n\nfunction normalizeKey(value: unknown, label: string): string {\n if (typeof value !== \"string\") fail(label, \" must be a string\");\n const normalized = value.replace(OWS_EDGES, \"\");\n if (normalized.length === 0) fail(label, \" must not be empty\");\n if (hasControl(normalized)) fail(label, \" must not contain control characters\");\n return normalized;\n}\n\n/** Parse a public auth source once and construct its private server policy. */\nexport function resolveInboundAuth(source: { value: unknown; label: string }): ResolvedInboundAuth {\n if (source.value === undefined) return { policy: DISABLED_POLICY };\n if (source.value === null || typeof source.value !== \"object\" || Array.isArray(source.value)) {\n fail(source.label, \" must be an object\");\n }\n const apiKeys = (source.value as { apiKeys?: unknown }).apiKeys;\n if (!Array.isArray(apiKeys)) fail(`${source.label}.apiKeys`, \" must be an array\");\n if (apiKeys.length === 0) fail(`${source.label}.apiKeys`, \" must not be empty\");\n const keys = apiKeys.map((value, index) =>\n normalizeKey(value, `${source.label}.apiKeys[${index}]`),\n );\n const seen = new Set<string>();\n for (const key of keys) {\n if (seen.has(key)) fail(source.label, \" must not contain duplicate API keys\");\n seen.add(key);\n }\n return {\n publicConfig: { apiKeys: keys },\n policy: { enabled: true, keys: keys.map((key) => Buffer.from(key, \"utf8\")) },\n };\n}\n\n/** Select configuration without parsing it, so an environment override bypasses malformed JSON auth. */\nexport function selectInboundAuthSource(\n configAuth: unknown,\n env: NodeJS.ProcessEnv = process.env,\n): { value: unknown; label: string } {\n if (Object.prototype.hasOwnProperty.call(env, \"AIMOCK_API_KEYS\")) {\n return { value: { apiKeys: (env.AIMOCK_API_KEYS ?? \"\").split(\",\") }, label: \"AIMOCK_API_KEYS\" };\n }\n return { value: configAuth, label: \"aimock.json.auth\" };\n}\n\nexport function isRecognizedApiKeyHeader(name: string): boolean {\n return RECOGNIZED_HEADERS.has(name.toLowerCase());\n}\n\nfunction rawCandidates(req: http.IncomingMessage): {\n candidates?: string[];\n reason?: \"missing\" | \"invalid\";\n} {\n const candidates: string[] = [];\n for (let index = 0; index < req.rawHeaders.length; index += 2) {\n const name = req.rawHeaders[index];\n const value = req.rawHeaders[index + 1] ?? \"\";\n const lower = name.toLowerCase();\n if (!isRecognizedApiKeyHeader(lower)) continue;\n if (hasControl(value)) return { reason: \"invalid\" };\n if (lower === \"authorization\") {\n const match = /^(?:Bearer|Key)[ \\t]+(.+)$/i.exec(value.replace(OWS_EDGES, \"\"));\n if (!match) return { reason: \"invalid\" };\n const candidate = match[1].replace(OWS_EDGES, \"\");\n if (candidate.length === 0 || hasControl(candidate)) return { reason: \"invalid\" };\n candidates.push(candidate);\n } else {\n const candidate = value.replace(OWS_EDGES, \"\");\n if (candidate.length === 0) return { reason: \"invalid\" };\n candidates.push(candidate);\n }\n }\n return candidates.length > 0 ? { candidates } : { reason: \"missing\" };\n}\n\nfunction matchingIndex(candidate: string, keys: readonly Buffer[]): number | undefined {\n const encoded = Buffer.from(candidate, \"utf8\");\n for (let index = 0; index < keys.length; index += 1) {\n const key = keys[index];\n if (encoded.length === key.length && timingSafeEqual(encoded, key)) return index;\n }\n return undefined;\n}\n\nexport function validateRequestApiKey(req: http.IncomingMessage, policy: ApiKeyPolicy): AuthResult {\n if (!policy.enabled) return { ok: true };\n const extracted = rawCandidates(req);\n if (!extracted.candidates) return { ok: false, reason: extracted.reason };\n let resolved: number | undefined;\n for (const candidate of extracted.candidates) {\n const index = matchingIndex(candidate, policy.keys);\n if (index === undefined) return { ok: false, reason: \"invalid\" };\n if (resolved !== undefined && resolved !== index) return { ok: false, reason: \"conflict\" };\n resolved = index;\n }\n return { ok: true };\n}\n\n/** @internal Opaque egress marker; it deliberately carries no key material. */\nexport function markAuthenticatedRequest(req: http.IncomingMessage, policy: ApiKeyPolicy): void {\n if (policy.enabled) authenticatedRequests.add(req);\n}\n\n/** @internal Used by egress paths to select safe credential handling. */\nexport function isAuthenticatedRequest(req: http.IncomingMessage): boolean {\n return authenticatedRequests.has(req);\n}\n\nexport function writeApiKeyHttpRejection(\n res: http.ServerResponse,\n setCorsHeaders: (res: http.ServerResponse) => void,\n): void {\n setCorsHeaders(res);\n res.writeHead(401, {\n \"Content-Type\": \"application/json\",\n \"Content-Length\": Buffer.byteLength(API_KEY_ERROR_BODY),\n \"WWW-Authenticate\": 'Bearer realm=\"aimock\"',\n });\n res.end(API_KEY_ERROR_BODY);\n}\n\nexport function writeApiKeyUpgradeRejection(socket: net.Socket): void {\n socket.write(\n [\n \"HTTP/1.1 401 Unauthorized\",\n \"Content-Type: application/json\",\n `Content-Length: ${Buffer.byteLength(API_KEY_ERROR_BODY)}`,\n 'WWW-Authenticate: Bearer realm=\"aimock\"',\n \"Connection: close\",\n \"\",\n API_KEY_ERROR_BODY,\n ].join(\"\\r\\n\"),\n );\n socket.destroy();\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { ApiKeyAuthConfig } from "./types.cjs";
|
|
2
|
+
|
|
3
|
+
//#region src/api-key-auth.d.ts
|
|
4
|
+
|
|
5
|
+
/** @internal */
|
|
6
|
+
interface ApiKeyPolicy {
|
|
7
|
+
readonly enabled: boolean;
|
|
8
|
+
readonly keys: readonly Buffer[];
|
|
9
|
+
}
|
|
10
|
+
/** @internal */
|
|
11
|
+
interface ResolvedInboundAuth {
|
|
12
|
+
readonly publicConfig?: ApiKeyAuthConfig;
|
|
13
|
+
readonly policy: ApiKeyPolicy;
|
|
14
|
+
}
|
|
15
|
+
//#endregion
|
|
16
|
+
export { ResolvedInboundAuth };
|
|
17
|
+
//# sourceMappingURL=api-key-auth.d.cts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api-key-auth.d.cts","names":[],"sources":["../src/api-key-auth.ts"],"sourcesContent":[],"mappings":";;;;;AA+B0B,UAPT,YAAA,CAOS;WACP,OAAA,EAAA,OAAA;EAAY,SAAA,IAAA,EAAA,SANL,MAMK,EAAA;;;UAFd,mBAAA;0BACS;mBACP"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { ApiKeyAuthConfig } from "./types.js";
|
|
2
|
+
|
|
3
|
+
//#region src/api-key-auth.d.ts
|
|
4
|
+
|
|
5
|
+
/** @internal */
|
|
6
|
+
interface ApiKeyPolicy {
|
|
7
|
+
readonly enabled: boolean;
|
|
8
|
+
readonly keys: readonly Buffer[];
|
|
9
|
+
}
|
|
10
|
+
/** @internal */
|
|
11
|
+
interface ResolvedInboundAuth {
|
|
12
|
+
readonly publicConfig?: ApiKeyAuthConfig;
|
|
13
|
+
readonly policy: ApiKeyPolicy;
|
|
14
|
+
}
|
|
15
|
+
//#endregion
|
|
16
|
+
export { ResolvedInboundAuth };
|
|
17
|
+
//# sourceMappingURL=api-key-auth.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api-key-auth.d.ts","names":[],"sources":["../src/api-key-auth.ts"],"sourcesContent":[],"mappings":";;;;;AA+B0B,UAPT,YAAA,CAOS;WACP,OAAA,EAAA,OAAA;EAAY,SAAA,IAAA,EAAA,SANL,MAMK,EAAA;;;UAFd,mBAAA;0BACS;mBACP"}
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import { timingSafeEqual } from "node:crypto";
|
|
2
|
+
|
|
3
|
+
//#region src/api-key-auth.ts
|
|
4
|
+
const INBOUND_API_KEY_HEADERS = [
|
|
5
|
+
"authorization",
|
|
6
|
+
"x-api-key",
|
|
7
|
+
"x-goog-api-key",
|
|
8
|
+
"api-key",
|
|
9
|
+
"xi-api-key"
|
|
10
|
+
];
|
|
11
|
+
const RECOGNIZED_HEADERS = new Set(INBOUND_API_KEY_HEADERS);
|
|
12
|
+
const API_KEY_ERROR_BODY = JSON.stringify({ error: {
|
|
13
|
+
message: "Invalid API key",
|
|
14
|
+
type: "authentication_error",
|
|
15
|
+
code: "invalid_api_key"
|
|
16
|
+
} });
|
|
17
|
+
const DISABLED_POLICY = {
|
|
18
|
+
enabled: false,
|
|
19
|
+
keys: []
|
|
20
|
+
};
|
|
21
|
+
const authenticatedRequests = /* @__PURE__ */ new WeakSet();
|
|
22
|
+
const OWS_EDGES = /^[ \t]+|[ \t]+$/g;
|
|
23
|
+
function fail(label, suffix) {
|
|
24
|
+
throw new Error(`${label}${suffix}`);
|
|
25
|
+
}
|
|
26
|
+
function hasControl(value) {
|
|
27
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
28
|
+
const code = value.charCodeAt(index);
|
|
29
|
+
if (code <= 31 || code === 127) return true;
|
|
30
|
+
}
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
function normalizeKey(value, label) {
|
|
34
|
+
if (typeof value !== "string") fail(label, " must be a string");
|
|
35
|
+
const normalized = value.replace(OWS_EDGES, "");
|
|
36
|
+
if (normalized.length === 0) fail(label, " must not be empty");
|
|
37
|
+
if (hasControl(normalized)) fail(label, " must not contain control characters");
|
|
38
|
+
return normalized;
|
|
39
|
+
}
|
|
40
|
+
/** Parse a public auth source once and construct its private server policy. */
|
|
41
|
+
function resolveInboundAuth(source) {
|
|
42
|
+
if (source.value === void 0) return { policy: DISABLED_POLICY };
|
|
43
|
+
if (source.value === null || typeof source.value !== "object" || Array.isArray(source.value)) fail(source.label, " must be an object");
|
|
44
|
+
const apiKeys = source.value.apiKeys;
|
|
45
|
+
if (!Array.isArray(apiKeys)) fail(`${source.label}.apiKeys`, " must be an array");
|
|
46
|
+
if (apiKeys.length === 0) fail(`${source.label}.apiKeys`, " must not be empty");
|
|
47
|
+
const keys = apiKeys.map((value, index) => normalizeKey(value, `${source.label}.apiKeys[${index}]`));
|
|
48
|
+
const seen = /* @__PURE__ */ new Set();
|
|
49
|
+
for (const key of keys) {
|
|
50
|
+
if (seen.has(key)) fail(source.label, " must not contain duplicate API keys");
|
|
51
|
+
seen.add(key);
|
|
52
|
+
}
|
|
53
|
+
return {
|
|
54
|
+
publicConfig: { apiKeys: keys },
|
|
55
|
+
policy: {
|
|
56
|
+
enabled: true,
|
|
57
|
+
keys: keys.map((key) => Buffer.from(key, "utf8"))
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
/** Select configuration without parsing it, so an environment override bypasses malformed JSON auth. */
|
|
62
|
+
function selectInboundAuthSource(configAuth, env = process.env) {
|
|
63
|
+
if (Object.prototype.hasOwnProperty.call(env, "AIMOCK_API_KEYS")) return {
|
|
64
|
+
value: { apiKeys: (env.AIMOCK_API_KEYS ?? "").split(",") },
|
|
65
|
+
label: "AIMOCK_API_KEYS"
|
|
66
|
+
};
|
|
67
|
+
return {
|
|
68
|
+
value: configAuth,
|
|
69
|
+
label: "aimock.json.auth"
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
function isRecognizedApiKeyHeader(name) {
|
|
73
|
+
return RECOGNIZED_HEADERS.has(name.toLowerCase());
|
|
74
|
+
}
|
|
75
|
+
function rawCandidates(req) {
|
|
76
|
+
const candidates = [];
|
|
77
|
+
for (let index = 0; index < req.rawHeaders.length; index += 2) {
|
|
78
|
+
const name = req.rawHeaders[index];
|
|
79
|
+
const value = req.rawHeaders[index + 1] ?? "";
|
|
80
|
+
const lower = name.toLowerCase();
|
|
81
|
+
if (!isRecognizedApiKeyHeader(lower)) continue;
|
|
82
|
+
if (hasControl(value)) return { reason: "invalid" };
|
|
83
|
+
if (lower === "authorization") {
|
|
84
|
+
const match = /^(?:Bearer|Key)[ \t]+(.+)$/i.exec(value.replace(OWS_EDGES, ""));
|
|
85
|
+
if (!match) return { reason: "invalid" };
|
|
86
|
+
const candidate = match[1].replace(OWS_EDGES, "");
|
|
87
|
+
if (candidate.length === 0 || hasControl(candidate)) return { reason: "invalid" };
|
|
88
|
+
candidates.push(candidate);
|
|
89
|
+
} else {
|
|
90
|
+
const candidate = value.replace(OWS_EDGES, "");
|
|
91
|
+
if (candidate.length === 0) return { reason: "invalid" };
|
|
92
|
+
candidates.push(candidate);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return candidates.length > 0 ? { candidates } : { reason: "missing" };
|
|
96
|
+
}
|
|
97
|
+
function matchingIndex(candidate, keys) {
|
|
98
|
+
const encoded = Buffer.from(candidate, "utf8");
|
|
99
|
+
for (let index = 0; index < keys.length; index += 1) {
|
|
100
|
+
const key = keys[index];
|
|
101
|
+
if (encoded.length === key.length && timingSafeEqual(encoded, key)) return index;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
function validateRequestApiKey(req, policy) {
|
|
105
|
+
if (!policy.enabled) return { ok: true };
|
|
106
|
+
const extracted = rawCandidates(req);
|
|
107
|
+
if (!extracted.candidates) return {
|
|
108
|
+
ok: false,
|
|
109
|
+
reason: extracted.reason
|
|
110
|
+
};
|
|
111
|
+
let resolved;
|
|
112
|
+
for (const candidate of extracted.candidates) {
|
|
113
|
+
const index = matchingIndex(candidate, policy.keys);
|
|
114
|
+
if (index === void 0) return {
|
|
115
|
+
ok: false,
|
|
116
|
+
reason: "invalid"
|
|
117
|
+
};
|
|
118
|
+
if (resolved !== void 0 && resolved !== index) return {
|
|
119
|
+
ok: false,
|
|
120
|
+
reason: "conflict"
|
|
121
|
+
};
|
|
122
|
+
resolved = index;
|
|
123
|
+
}
|
|
124
|
+
return { ok: true };
|
|
125
|
+
}
|
|
126
|
+
/** @internal Opaque egress marker; it deliberately carries no key material. */
|
|
127
|
+
function markAuthenticatedRequest(req, policy) {
|
|
128
|
+
if (policy.enabled) authenticatedRequests.add(req);
|
|
129
|
+
}
|
|
130
|
+
/** @internal Used by egress paths to select safe credential handling. */
|
|
131
|
+
function isAuthenticatedRequest(req) {
|
|
132
|
+
return authenticatedRequests.has(req);
|
|
133
|
+
}
|
|
134
|
+
function writeApiKeyHttpRejection(res, setCorsHeaders) {
|
|
135
|
+
setCorsHeaders(res);
|
|
136
|
+
res.writeHead(401, {
|
|
137
|
+
"Content-Type": "application/json",
|
|
138
|
+
"Content-Length": Buffer.byteLength(API_KEY_ERROR_BODY),
|
|
139
|
+
"WWW-Authenticate": "Bearer realm=\"aimock\""
|
|
140
|
+
});
|
|
141
|
+
res.end(API_KEY_ERROR_BODY);
|
|
142
|
+
}
|
|
143
|
+
function writeApiKeyUpgradeRejection(socket) {
|
|
144
|
+
socket.write([
|
|
145
|
+
"HTTP/1.1 401 Unauthorized",
|
|
146
|
+
"Content-Type: application/json",
|
|
147
|
+
`Content-Length: ${Buffer.byteLength(API_KEY_ERROR_BODY)}`,
|
|
148
|
+
"WWW-Authenticate: Bearer realm=\"aimock\"",
|
|
149
|
+
"Connection: close",
|
|
150
|
+
"",
|
|
151
|
+
API_KEY_ERROR_BODY
|
|
152
|
+
].join("\r\n"));
|
|
153
|
+
socket.destroy();
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
//#endregion
|
|
157
|
+
export { isAuthenticatedRequest, isRecognizedApiKeyHeader, markAuthenticatedRequest, resolveInboundAuth, selectInboundAuthSource, validateRequestApiKey, writeApiKeyHttpRejection, writeApiKeyUpgradeRejection };
|
|
158
|
+
//# sourceMappingURL=api-key-auth.js.map
|