@oh-my-pi/pi-ai 18.2.7 → 18.2.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (93) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/dist/types/auth-gateway/dispatch.d.ts +80 -0
  3. package/dist/types/auth-gateway/http.d.ts +5 -6
  4. package/dist/types/auth-gateway/index.d.ts +1 -0
  5. package/dist/types/auth-gateway/routes/embeddings.d.ts +3 -0
  6. package/dist/types/auth-gateway/routes/images.d.ts +3 -0
  7. package/dist/types/auth-gateway/routes/rerank.d.ts +3 -0
  8. package/dist/types/auth-gateway/routes/speech.d.ts +2 -0
  9. package/dist/types/auth-gateway/routes/systemone.d.ts +2 -0
  10. package/dist/types/auth-gateway/routes/transcriptions.d.ts +3 -0
  11. package/dist/types/auth-gateway/routes/video.d.ts +7 -0
  12. package/dist/types/auth-gateway/server.d.ts +10 -16
  13. package/dist/types/embeddings/index.d.ts +7 -0
  14. package/dist/types/embeddings/openai-embeddings.d.ts +15 -0
  15. package/dist/types/embeddings/types.d.ts +15 -0
  16. package/dist/types/images/google-antigravity.d.ts +9 -0
  17. package/dist/types/images/google-generative-ai.d.ts +3 -0
  18. package/dist/types/images/index.d.ts +14 -0
  19. package/dist/types/images/openai-hosted.d.ts +3 -0
  20. package/dist/types/images/openai-images.d.ts +5 -0
  21. package/dist/types/images/openrouter-images.d.ts +3 -0
  22. package/dist/types/images/shared.d.ts +34 -0
  23. package/dist/types/images/types.d.ts +31 -0
  24. package/dist/types/index.d.ts +7 -1
  25. package/dist/types/judgment/typesafe.d.ts +2 -0
  26. package/dist/types/providers/embeddings-server.d.ts +32 -0
  27. package/dist/types/providers/images-server.d.ts +22 -0
  28. package/dist/types/providers/rerank-server.d.ts +35 -0
  29. package/dist/types/providers/speech-server.d.ts +8 -0
  30. package/dist/types/providers/systemone-server.d.ts +26 -0
  31. package/dist/types/providers/transcriptions-server.d.ts +32 -0
  32. package/dist/types/providers/video-server.d.ts +39 -0
  33. package/dist/types/rerank/index.d.ts +7 -0
  34. package/dist/types/rerank/openrouter-rerank.d.ts +15 -0
  35. package/dist/types/rerank/types.d.ts +17 -0
  36. package/dist/types/speech/index.d.ts +13 -0
  37. package/dist/types/speech/openai-speech.d.ts +3 -0
  38. package/dist/types/speech/transport.d.ts +7 -0
  39. package/dist/types/speech/types.d.ts +24 -0
  40. package/dist/types/speech/xai-tts.d.ts +7 -0
  41. package/dist/types/transcription/index.d.ts +7 -0
  42. package/dist/types/transcription/openai-transcriptions.d.ts +15 -0
  43. package/dist/types/transcription/types.d.ts +41 -0
  44. package/dist/types/video/index.d.ts +11 -0
  45. package/dist/types/video/openrouter-video.d.ts +19 -0
  46. package/dist/types/video/types.d.ts +62 -0
  47. package/package.json +30 -6
  48. package/src/auth-gateway/dispatch.ts +273 -0
  49. package/src/auth-gateway/http.ts +6 -7
  50. package/src/auth-gateway/index.ts +1 -0
  51. package/src/auth-gateway/routes/embeddings.ts +98 -0
  52. package/src/auth-gateway/routes/images.ts +131 -0
  53. package/src/auth-gateway/routes/rerank.ts +87 -0
  54. package/src/auth-gateway/routes/speech.ts +101 -0
  55. package/src/auth-gateway/routes/systemone.ts +116 -0
  56. package/src/auth-gateway/routes/transcriptions.ts +98 -0
  57. package/src/auth-gateway/routes/video.ts +243 -0
  58. package/src/auth-gateway/server.ts +123 -260
  59. package/src/embeddings/index.ts +17 -0
  60. package/src/embeddings/openai-embeddings.ts +141 -0
  61. package/src/embeddings/types.ts +14 -0
  62. package/src/error/rate-limit.ts +1 -1
  63. package/src/images/google-antigravity.ts +180 -0
  64. package/src/images/google-generative-ai.ts +92 -0
  65. package/src/images/index.ts +59 -0
  66. package/src/images/openai-hosted.ts +185 -0
  67. package/src/images/openai-images.ts +110 -0
  68. package/src/images/openrouter-images.ts +33 -0
  69. package/src/images/shared.ts +193 -0
  70. package/src/images/types.ts +36 -0
  71. package/src/index.ts +7 -1
  72. package/src/judgment/typesafe.ts +5 -0
  73. package/src/providers/embeddings-server.ts +151 -0
  74. package/src/providers/images-server.ts +159 -0
  75. package/src/providers/rerank-server.ts +166 -0
  76. package/src/providers/speech-server.ts +53 -0
  77. package/src/providers/systemone-server.ts +73 -0
  78. package/src/providers/transcriptions-server.ts +243 -0
  79. package/src/providers/video-server.ts +286 -0
  80. package/src/rerank/index.ts +13 -0
  81. package/src/rerank/openrouter-rerank.ts +136 -0
  82. package/src/rerank/types.ts +20 -0
  83. package/src/speech/index.ts +35 -0
  84. package/src/speech/openai-speech.ts +26 -0
  85. package/src/speech/transport.ts +66 -0
  86. package/src/speech/types.ts +37 -0
  87. package/src/speech/xai-tts.ts +41 -0
  88. package/src/transcription/index.ts +17 -0
  89. package/src/transcription/openai-transcriptions.ts +133 -0
  90. package/src/transcription/types.ts +46 -0
  91. package/src/video/index.ts +34 -0
  92. package/src/video/openrouter-video.ts +210 -0
  93. package/src/video/types.ts +72 -0
@@ -0,0 +1,98 @@
1
+ import { logger } from "@oh-my-pi/pi-utils";
2
+ import { embed } from "../../embeddings";
3
+ import { classifyGatewayError } from "../../error/gateway";
4
+ import * as embeddings from "../../providers/embeddings-server";
5
+ import { deterministicUuid } from "../../utils/deterministic-id";
6
+ import {
7
+ type AuthGatewayBootOptions,
8
+ buildGatewayApiKeyResolver,
9
+ mirrorRequestAbort,
10
+ recordGatewayUsage,
11
+ resolveGatewayApiKey,
12
+ } from "../dispatch";
13
+ import { gatewayResponseHeaders, json, resolveClientIdentity } from "../http";
14
+
15
+ /** OpenAI-compatible `POST /v1/embeddings` gateway handler. */
16
+ export async function handleEmbeddings(
17
+ bootOpts: AuthGatewayBootOptions,
18
+ req: Request,
19
+ peer: string,
20
+ ): Promise<Response> {
21
+ const startedAt = performance.now();
22
+ const requestId = crypto.randomUUID();
23
+ const controller = mirrorRequestAbort(req);
24
+ const aborted = (): Response => embeddings.formatError(499, "request_aborted", "client closed request");
25
+ if (controller.signal.aborted) return aborted();
26
+
27
+ let parsed: embeddings.EmbeddingsParsedRequest;
28
+ try {
29
+ parsed = await embeddings.parseRequest(req);
30
+ } catch (error) {
31
+ if (controller.signal.aborted) return aborted();
32
+ const message = error instanceof Error ? error.message : String(error);
33
+ const status = error instanceof embeddings.EmbeddingsWireError ? error.status : 400;
34
+ return embeddings.formatError(status, "invalid_request_error", message);
35
+ }
36
+ if (controller.signal.aborted) return aborted();
37
+
38
+ const model = bootOpts.resolveModel(parsed.modelId);
39
+ if (!model) {
40
+ return embeddings.formatError(404, "invalid_request_error", `Unknown model: ${parsed.modelId}`);
41
+ }
42
+ if (model.api !== "openai-embeddings") {
43
+ return embeddings.formatError(
44
+ 400,
45
+ "invalid_request_error",
46
+ `Model ${parsed.modelId} does not support embeddings`,
47
+ );
48
+ }
49
+
50
+ const client = resolveClientIdentity(req.headers);
51
+ const sessionId = deterministicUuid(`embeddings\u0000${model.provider}/${model.id}`);
52
+ const apiKey = await resolveGatewayApiKey(bootOpts.storage, model, sessionId, controller.signal, peer);
53
+ if (controller.signal.aborted) return aborted();
54
+ if (typeof apiKey !== "string") {
55
+ return embeddings.formatError(apiKey.status, apiKey.type, apiKey.message);
56
+ }
57
+
58
+ logger.info("auth-gateway request", {
59
+ requestId,
60
+ format: "embeddings",
61
+ model: parsed.modelId,
62
+ resolvedProvider: model.provider,
63
+ resolvedModel: model.id,
64
+ stream: false,
65
+ peer,
66
+ });
67
+
68
+ try {
69
+ const result = await embed(model, parsed.request, {
70
+ apiKey: buildGatewayApiKeyResolver(
71
+ bootOpts.storage,
72
+ model,
73
+ sessionId,
74
+ apiKey,
75
+ controller.signal,
76
+ "embeddings",
77
+ peer,
78
+ ),
79
+ fetch: bootOpts.fetch,
80
+ signal: controller.signal,
81
+ });
82
+ recordGatewayUsage(bootOpts.storage, model, client, result.usage);
83
+ return json(
84
+ 200,
85
+ embeddings.encodeResponse(result, parsed.modelId),
86
+ gatewayResponseHeaders(model, { requestId, costUsd: result.usage.cost.total, startedAt }),
87
+ );
88
+ } catch (error) {
89
+ if (controller.signal.aborted) return aborted();
90
+ const classified = classifyGatewayError(error);
91
+ logger.warn("auth-gateway embeddings failed", {
92
+ format: "embeddings",
93
+ error: classified.message,
94
+ peer,
95
+ });
96
+ return embeddings.formatError(classified.status, classified.type, classified.message);
97
+ }
98
+ }
@@ -0,0 +1,131 @@
1
+ import { calculateCost } from "@oh-my-pi/pi-catalog/models";
2
+ import { logger } from "@oh-my-pi/pi-utils";
3
+ import { classifyGatewayError } from "../../error/gateway";
4
+ import { generateImage } from "../../images";
5
+ import * as imagesServer from "../../providers/images-server";
6
+ import { deterministicUuid } from "../../utils/deterministic-id";
7
+ import {
8
+ type AuthGatewayBootOptions,
9
+ buildGatewayApiKeyResolver,
10
+ mirrorRequestAbort,
11
+ recordGatewayUsage,
12
+ resolveGatewayApiKey,
13
+ } from "../dispatch";
14
+ import { gatewayResponseHeaders, json, resolveClientIdentity } from "../http";
15
+
16
+ function isGatewayImageApi(api: string): boolean {
17
+ return (
18
+ api === "openai-images" ||
19
+ api === "openrouter-images" ||
20
+ api === "google-generative-ai" ||
21
+ api === "google-gemini-cli"
22
+ );
23
+ }
24
+
25
+ async function handleImages(
26
+ bootOpts: AuthGatewayBootOptions,
27
+ req: Request,
28
+ peer: string,
29
+ kind: imagesServer.ImageRequestKind,
30
+ ): Promise<Response> {
31
+ const startedAt = performance.now();
32
+ const requestId = crypto.randomUUID();
33
+ const controller = mirrorRequestAbort(req);
34
+ const aborted = (): Response => imagesServer.formatError(499, "request_aborted", "client closed request");
35
+ if (controller.signal.aborted) return aborted();
36
+
37
+ let body: unknown | FormData;
38
+ try {
39
+ body =
40
+ kind === "edits" && req.headers.get("content-type")?.includes("multipart/form-data")
41
+ ? await req.formData()
42
+ : await req.json();
43
+ } catch (error) {
44
+ if (controller.signal.aborted) return aborted();
45
+ return imagesServer.formatError(400, "invalid_request_error", `Invalid request body: ${String(error)}`);
46
+ }
47
+ if (controller.signal.aborted) return aborted();
48
+
49
+ let parsed: imagesServer.ImagesParsedRequest;
50
+ try {
51
+ parsed = await imagesServer.parseRequest(body, kind);
52
+ } catch (error) {
53
+ const message = error instanceof Error ? error.message : String(error);
54
+ return imagesServer.formatError(400, "invalid_request_error", message);
55
+ }
56
+
57
+ const model = bootOpts.resolveModel(parsed.modelId);
58
+ if (!model) {
59
+ return imagesServer.formatError(404, "invalid_request_error", `Unknown model: ${parsed.modelId}`);
60
+ }
61
+ if (model.api === "openai-responses" || model.api === "openai-codex-responses") {
62
+ return imagesServer.formatError(
63
+ 400,
64
+ "invalid_request_error",
65
+ `Model ${parsed.modelId} requires a hosted image carrier, which the auth gateway cannot resolve`,
66
+ );
67
+ }
68
+ if (!isGatewayImageApi(model.api)) {
69
+ return imagesServer.formatError(
70
+ 400,
71
+ "invalid_request_error",
72
+ `Model ${parsed.modelId} does not support image generation`,
73
+ );
74
+ }
75
+
76
+ const client = resolveClientIdentity(req.headers);
77
+ const sessionId = deterministicUuid(`images\u0000${model.provider}/${model.id}`);
78
+ const apiKey = await resolveGatewayApiKey(bootOpts.storage, model, sessionId, controller.signal, peer);
79
+ if (controller.signal.aborted) return aborted();
80
+ if (typeof apiKey !== "string") return imagesServer.formatError(apiKey.status, apiKey.type, apiKey.message);
81
+
82
+ logger.info("auth-gateway request", {
83
+ requestId,
84
+ format: "images",
85
+ model: parsed.modelId,
86
+ resolvedProvider: model.provider,
87
+ resolvedModel: model.id,
88
+ stream: false,
89
+ peer,
90
+ });
91
+
92
+ try {
93
+ const result = await generateImage(model, parsed.request, {
94
+ apiKey: buildGatewayApiKeyResolver(
95
+ bootOpts.storage,
96
+ model,
97
+ sessionId,
98
+ apiKey,
99
+ controller.signal,
100
+ "images",
101
+ peer,
102
+ ),
103
+ fetch: bootOpts.fetch,
104
+ signal: controller.signal,
105
+ });
106
+ if (result.usage.cost.total === 0) calculateCost(model, result.usage);
107
+ recordGatewayUsage(bootOpts.storage, model, client, result.usage);
108
+ return json(
109
+ 200,
110
+ imagesServer.encodeResponse(result, parsed.modelId),
111
+ gatewayResponseHeaders(model, { requestId, costUsd: result.usage.cost.total, startedAt }),
112
+ );
113
+ } catch (error) {
114
+ if (controller.signal.aborted) return aborted();
115
+ const classified = classifyGatewayError(error);
116
+ logger.warn("auth-gateway image generation failed", { format: "images", error: classified.message, peer });
117
+ return imagesServer.formatError(classified.status, classified.type, classified.message);
118
+ }
119
+ }
120
+
121
+ export function handleImageGenerations(
122
+ bootOpts: AuthGatewayBootOptions,
123
+ req: Request,
124
+ peer: string,
125
+ ): Promise<Response> {
126
+ return handleImages(bootOpts, req, peer, "generations");
127
+ }
128
+
129
+ export function handleImageEdits(bootOpts: AuthGatewayBootOptions, req: Request, peer: string): Promise<Response> {
130
+ return handleImages(bootOpts, req, peer, "edits");
131
+ }
@@ -0,0 +1,87 @@
1
+ import { logger } from "@oh-my-pi/pi-utils";
2
+ import { classifyGatewayError } from "../../error/gateway";
3
+ import * as rerankWire from "../../providers/rerank-server";
4
+ import { rerank } from "../../rerank";
5
+ import { deterministicUuid } from "../../utils/deterministic-id";
6
+ import {
7
+ type AuthGatewayBootOptions,
8
+ buildGatewayApiKeyResolver,
9
+ mirrorRequestAbort,
10
+ recordGatewayUsage,
11
+ resolveGatewayApiKey,
12
+ } from "../dispatch";
13
+ import { gatewayResponseHeaders, json, resolveClientIdentity } from "../http";
14
+
15
+ /** OpenRouter-compatible `POST /v1/rerank` gateway handler. */
16
+ export async function handleRerank(bootOpts: AuthGatewayBootOptions, req: Request, peer: string): Promise<Response> {
17
+ const startedAt = performance.now();
18
+ const requestId = crypto.randomUUID();
19
+ const controller = mirrorRequestAbort(req);
20
+ const aborted = (): Response => rerankWire.formatError(499, "request_aborted", "client closed request");
21
+ if (controller.signal.aborted) return aborted();
22
+
23
+ let parsed: rerankWire.RerankParsedRequest;
24
+ try {
25
+ parsed = await rerankWire.parseRequest(req);
26
+ } catch (error) {
27
+ if (controller.signal.aborted) return aborted();
28
+ const message = error instanceof Error ? error.message : String(error);
29
+ const status = error instanceof rerankWire.RerankWireError ? error.status : 400;
30
+ return rerankWire.formatError(status, "invalid_request_error", message);
31
+ }
32
+ if (controller.signal.aborted) return aborted();
33
+
34
+ const model = bootOpts.resolveModel(parsed.modelId);
35
+ if (!model) return rerankWire.formatError(404, "invalid_request_error", `Unknown model: ${parsed.modelId}`);
36
+ if (model.api !== "openrouter-rerank") {
37
+ return rerankWire.formatError(400, "invalid_request_error", `Model ${parsed.modelId} does not support reranking`);
38
+ }
39
+
40
+ const client = resolveClientIdentity(req.headers);
41
+ const sessionId = deterministicUuid(`rerank\u0000${model.provider}/${model.id}`);
42
+ const apiKey = await resolveGatewayApiKey(bootOpts.storage, model, sessionId, controller.signal, peer);
43
+ if (controller.signal.aborted) return aborted();
44
+ if (typeof apiKey !== "string") return rerankWire.formatError(apiKey.status, apiKey.type, apiKey.message);
45
+
46
+ logger.info("auth-gateway request", {
47
+ requestId,
48
+ format: "rerank",
49
+ model: parsed.modelId,
50
+ resolvedProvider: model.provider,
51
+ resolvedModel: model.id,
52
+ stream: false,
53
+ peer,
54
+ });
55
+
56
+ try {
57
+ const result = await rerank(model, parsed.request, {
58
+ apiKey: buildGatewayApiKeyResolver(
59
+ bootOpts.storage,
60
+ model,
61
+ sessionId,
62
+ apiKey,
63
+ controller.signal,
64
+ "rerank",
65
+ peer,
66
+ ),
67
+ fetch: bootOpts.fetch,
68
+ signal: controller.signal,
69
+ });
70
+ recordGatewayUsage(bootOpts.storage, model, client, result.usage);
71
+ return json(
72
+ 200,
73
+ rerankWire.encodeResponse(
74
+ result,
75
+ parsed.modelId,
76
+ parsed.originalDocuments,
77
+ parsed.request.returnDocuments ?? false,
78
+ ),
79
+ gatewayResponseHeaders(model, { requestId, costUsd: result.usage.cost.total, startedAt }),
80
+ );
81
+ } catch (error) {
82
+ if (controller.signal.aborted) return aborted();
83
+ const classified = classifyGatewayError(error);
84
+ logger.warn("auth-gateway rerank failed", { format: "rerank", error: classified.message, peer });
85
+ return rerankWire.formatError(classified.status, classified.type, classified.message);
86
+ }
87
+ }
@@ -0,0 +1,101 @@
1
+ import { logger } from "@oh-my-pi/pi-utils";
2
+ import { classifyGatewayError } from "../../error/gateway";
3
+ import * as speechWire from "../../providers/speech-server";
4
+ import { isSpeechApi, synthesizeSpeech } from "../../speech";
5
+ import { deterministicUuid } from "../../utils/deterministic-id";
6
+ import {
7
+ type AuthGatewayBootOptions,
8
+ buildGatewayApiKeyResolver,
9
+ mirrorRequestAbort,
10
+ recordGatewayUsage,
11
+ resolveGatewayApiKey,
12
+ } from "../dispatch";
13
+ import { gatewayResponseHeaders, resolveClientIdentity } from "../http";
14
+
15
+ export async function handleSpeech(bootOpts: AuthGatewayBootOptions, req: Request, peer: string): Promise<Response> {
16
+ const startedAt = performance.now();
17
+ const requestId = crypto.randomUUID();
18
+ const controller = mirrorRequestAbort(req);
19
+ const aborted = (): Response => speechWire.formatError(499, "request_aborted", "client closed request");
20
+ if (controller.signal.aborted) return aborted();
21
+
22
+ let body: unknown;
23
+ try {
24
+ body = await req.json();
25
+ } catch (error) {
26
+ if (controller.signal.aborted) return aborted();
27
+ return speechWire.formatError(400, "invalid_request_error", `Invalid JSON body: ${String(error)}`);
28
+ }
29
+ if (controller.signal.aborted) return aborted();
30
+
31
+ let parsed: speechWire.SpeechParsedRequest;
32
+ try {
33
+ parsed = speechWire.parseRequest(body, req.headers);
34
+ } catch (error) {
35
+ const message = error instanceof Error ? error.message : String(error);
36
+ return speechWire.formatError(400, "invalid_request_error", message);
37
+ }
38
+
39
+ const model = bootOpts.resolveModel(parsed.modelId);
40
+ if (!model) return speechWire.formatError(404, "invalid_request_error", `Unknown model: ${parsed.modelId}`);
41
+ if (!isSpeechApi(model.api)) {
42
+ const route =
43
+ model.kind === "image"
44
+ ? "/v1/images/generations"
45
+ : model.kind === "stt"
46
+ ? "/v1/audio/transcriptions"
47
+ : model.kind === "judge"
48
+ ? "/v1/systemone"
49
+ : undefined;
50
+ const message = route
51
+ ? `Model ${parsed.modelId} does not synthesize speech; use POST ${route}`
52
+ : `Model ${parsed.modelId} does not synthesize speech`;
53
+ return speechWire.formatError(400, "invalid_request_error", message);
54
+ }
55
+
56
+ const client = resolveClientIdentity(req.headers);
57
+ const sessionId = deterministicUuid(`speech\u0000${model.provider}/${model.id}`);
58
+ const apiKey = await resolveGatewayApiKey(bootOpts.storage, model, sessionId, controller.signal, peer);
59
+ if (controller.signal.aborted) return aborted();
60
+ if (typeof apiKey !== "string") return speechWire.formatError(apiKey.status, apiKey.type, apiKey.message);
61
+
62
+ logger.info("auth-gateway request", {
63
+ requestId,
64
+ format: "speech",
65
+ model: parsed.modelId,
66
+ resolvedProvider: model.provider,
67
+ resolvedModel: model.id,
68
+ stream: false,
69
+ peer,
70
+ });
71
+
72
+ try {
73
+ const result = await synthesizeSpeech(model, parsed.request, {
74
+ apiKey: buildGatewayApiKeyResolver(
75
+ bootOpts.storage,
76
+ model,
77
+ sessionId,
78
+ apiKey,
79
+ controller.signal,
80
+ "speech",
81
+ peer,
82
+ ),
83
+ fetch: bootOpts.fetch,
84
+ signal: controller.signal,
85
+ });
86
+ recordGatewayUsage(bootOpts.storage, model, client, result.usage);
87
+ const response = speechWire.encodeResponse(result, parsed.modelId);
88
+ const responseHeaders = gatewayResponseHeaders(model, {
89
+ requestId,
90
+ costUsd: result.usage.cost.total,
91
+ startedAt,
92
+ });
93
+ for (const name in responseHeaders) response.headers.set(name, responseHeaders[name]);
94
+ return response;
95
+ } catch (error) {
96
+ if (controller.signal.aborted) return aborted();
97
+ const classified = classifyGatewayError(error);
98
+ logger.warn("auth-gateway speech failed", { format: "speech", error: classified.message, peer });
99
+ return speechWire.formatError(classified.status, classified.type, classified.message);
100
+ }
101
+ }
@@ -0,0 +1,116 @@
1
+ /**
2
+ * TypeSafe System One judgments: `POST /v1/systemone` (TypeSafe's path) and
3
+ * `POST /alpha/decisions` (OpenRouter's Decisions path, same wire).
4
+ *
5
+ * Same bearer auth, model resolution, broker credential lookup, and a/b/c
6
+ * credential rotation as the chat routes, dispatched through
7
+ * {@link TypeSafeJudge} — pi-ai's client for this wire — so 401 rotation and
8
+ * 429/529 backoff match omp's own judgments. A judgment is one JSON
9
+ * round-trip: no stream, no provider session state. TypeSafe reports tokens
10
+ * only, so the cost is priced from the catalog model for the response header
11
+ * and the broker's observed-usage ledger.
12
+ */
13
+ import { calculateCost } from "@oh-my-pi/pi-catalog/models";
14
+ import { logger } from "@oh-my-pi/pi-utils";
15
+ import { classifyGatewayError } from "../../error/gateway";
16
+ import { isJudgmentApi, TypeSafeJudge } from "../../judgment/typesafe";
17
+ import * as systemOne from "../../providers/systemone-server";
18
+ import { deterministicUuid } from "../../utils/deterministic-id";
19
+ import {
20
+ type AuthGatewayBootOptions,
21
+ buildGatewayApiKeyResolver,
22
+ mirrorRequestAbort,
23
+ recordGatewayUsage,
24
+ resolveGatewayApiKey,
25
+ } from "../dispatch";
26
+ import { gatewayResponseHeaders, json, resolveClientIdentity } from "../http";
27
+
28
+ export async function handleSystemOne(bootOpts: AuthGatewayBootOptions, req: Request, peer: string): Promise<Response> {
29
+ const startedAt = performance.now();
30
+ const requestId = crypto.randomUUID();
31
+ const controller = mirrorRequestAbort(req);
32
+ const aborted = (): Response => systemOne.formatError(499, "request_aborted", "client closed request");
33
+ if (controller.signal.aborted) return aborted();
34
+
35
+ let body: unknown;
36
+ try {
37
+ body = await req.json();
38
+ } catch (error) {
39
+ if (controller.signal.aborted) return aborted();
40
+ return systemOne.formatError(400, "invalid_request_error", `Invalid JSON body: ${String(error)}`);
41
+ }
42
+ if (controller.signal.aborted) return aborted();
43
+
44
+ let parsed: systemOne.SystemOneParsedRequest;
45
+ try {
46
+ parsed = systemOne.parseRequest(body);
47
+ } catch (error) {
48
+ const message = error instanceof Error ? error.message : String(error);
49
+ // TypeSafe answers body validation failures with 422.
50
+ return systemOne.formatError(422, "invalid_request_error", message);
51
+ }
52
+
53
+ const model = bootOpts.resolveModel(parsed.modelId);
54
+ if (!model) {
55
+ return systemOne.formatError(404, "invalid_request_error", `Unknown model: ${parsed.modelId}`);
56
+ }
57
+ if (!isJudgmentApi(model.api)) {
58
+ return systemOne.formatError(
59
+ 422,
60
+ "invalid_request_error",
61
+ `Model ${parsed.modelId} does not answer System One judgments`,
62
+ );
63
+ }
64
+ const client = resolveClientIdentity(req.headers);
65
+ // Judgments carry no conversation to derive a session from. One sticky
66
+ // session per model keeps credential selection stable across calls and
67
+ // still lets `markUsageLimitReached` hand off to a sibling account.
68
+ const sessionId = deterministicUuid(`systemone\u0000${model.provider}/${model.id}`);
69
+
70
+ const apiKey = await resolveGatewayApiKey(bootOpts.storage, model, sessionId, controller.signal, peer);
71
+ if (controller.signal.aborted) return aborted();
72
+ if (typeof apiKey !== "string") return systemOne.formatError(apiKey.status, apiKey.type, apiKey.message);
73
+
74
+ const judge = new TypeSafeJudge({
75
+ apiKey: buildGatewayApiKeyResolver(
76
+ bootOpts.storage,
77
+ model,
78
+ sessionId,
79
+ apiKey,
80
+ controller.signal,
81
+ "systemone",
82
+ peer,
83
+ ),
84
+ api: model.api,
85
+ provider: model.provider,
86
+ model: model.id,
87
+ baseUrl: model.baseUrl,
88
+ fetch: bootOpts.fetch,
89
+ });
90
+
91
+ logger.info("auth-gateway request", {
92
+ requestId,
93
+ format: "systemone",
94
+ model: parsed.modelId,
95
+ resolvedProvider: model.provider,
96
+ resolvedModel: model.id,
97
+ stream: false,
98
+ peer,
99
+ });
100
+
101
+ try {
102
+ const result = await judge.judge(parsed.request, { signal: controller.signal });
103
+ // Encode before pricing: the body's `cost` means "upstream billed this"
104
+ // (OpenRouter), while the header and ledger carry the catalog estimate
105
+ // when the upstream reported tokens only (TypeSafe).
106
+ const body = systemOne.encodeResponse(result);
107
+ if (result.usage.cost.total === 0) calculateCost(model, result.usage);
108
+ recordGatewayUsage(bootOpts.storage, model, client, result.usage);
109
+ return json(200, body, gatewayResponseHeaders(model, { requestId, costUsd: result.usage.cost.total, startedAt }));
110
+ } catch (error) {
111
+ if (controller.signal.aborted) return aborted();
112
+ const classified = classifyGatewayError(error);
113
+ logger.warn("auth-gateway judgment failed", { format: "systemone", error: classified.message, peer });
114
+ return systemOne.formatError(classified.status, classified.type, classified.message);
115
+ }
116
+ }
@@ -0,0 +1,98 @@
1
+ import { logger } from "@oh-my-pi/pi-utils";
2
+ import { classifyGatewayError } from "../../error/gateway";
3
+ import * as transcriptions from "../../providers/transcriptions-server";
4
+ import { transcribeAudio } from "../../transcription";
5
+ import { deterministicUuid } from "../../utils/deterministic-id";
6
+ import {
7
+ type AuthGatewayBootOptions,
8
+ buildGatewayApiKeyResolver,
9
+ mirrorRequestAbort,
10
+ recordGatewayUsage,
11
+ resolveGatewayApiKey,
12
+ } from "../dispatch";
13
+ import { gatewayResponseHeaders, json, resolveClientIdentity } from "../http";
14
+
15
+ /** OpenAI-compatible `POST /v1/audio/transcriptions` gateway handler. */
16
+ export async function handleTranscriptions(
17
+ bootOpts: AuthGatewayBootOptions,
18
+ req: Request,
19
+ peer: string,
20
+ ): Promise<Response> {
21
+ const startedAt = performance.now();
22
+ const requestId = crypto.randomUUID();
23
+ const controller = mirrorRequestAbort(req);
24
+ const aborted = (): Response => transcriptions.formatError(499, "request_aborted", "client closed request");
25
+ if (controller.signal.aborted) return aborted();
26
+
27
+ let parsed: transcriptions.TranscriptionParsedRequest;
28
+ try {
29
+ parsed = await transcriptions.parseRequest(req);
30
+ } catch (error) {
31
+ if (controller.signal.aborted) return aborted();
32
+ const message = error instanceof Error ? error.message : String(error);
33
+ const status = error instanceof transcriptions.TranscriptionWireError ? error.status : 400;
34
+ return transcriptions.formatError(status, "invalid_request_error", message);
35
+ }
36
+ if (controller.signal.aborted) return aborted();
37
+
38
+ const model = bootOpts.resolveModel(parsed.modelId);
39
+ if (!model) {
40
+ return transcriptions.formatError(404, "invalid_request_error", `Unknown model: ${parsed.modelId}`);
41
+ }
42
+ if (model.api !== "openai-transcriptions") {
43
+ const detail =
44
+ model.api === "local-inference"
45
+ ? `Model ${parsed.modelId} runs on-device only and cannot be served by the auth gateway`
46
+ : `Model ${parsed.modelId} does not support audio transcription`;
47
+ return transcriptions.formatError(400, "invalid_request_error", detail);
48
+ }
49
+
50
+ const client = resolveClientIdentity(req.headers);
51
+ const sessionId = deterministicUuid(`transcriptions\u0000${model.provider}/${model.id}`);
52
+ const apiKey = await resolveGatewayApiKey(bootOpts.storage, model, sessionId, controller.signal, peer);
53
+ if (controller.signal.aborted) return aborted();
54
+ if (typeof apiKey !== "string") {
55
+ return transcriptions.formatError(apiKey.status, apiKey.type, apiKey.message);
56
+ }
57
+
58
+ logger.info("auth-gateway request", {
59
+ requestId,
60
+ format: "transcriptions",
61
+ model: parsed.modelId,
62
+ resolvedProvider: model.provider,
63
+ resolvedModel: model.id,
64
+ stream: false,
65
+ peer,
66
+ });
67
+
68
+ try {
69
+ const result = await transcribeAudio(model, parsed.request, {
70
+ apiKey: buildGatewayApiKeyResolver(
71
+ bootOpts.storage,
72
+ model,
73
+ sessionId,
74
+ apiKey,
75
+ controller.signal,
76
+ "transcriptions",
77
+ peer,
78
+ ),
79
+ fetch: bootOpts.fetch,
80
+ signal: controller.signal,
81
+ });
82
+ recordGatewayUsage(bootOpts.storage, model, client, result.usage);
83
+ return json(
84
+ 200,
85
+ transcriptions.encodeResponse(result),
86
+ gatewayResponseHeaders(model, { requestId, costUsd: result.usage.cost.total, startedAt }),
87
+ );
88
+ } catch (error) {
89
+ if (controller.signal.aborted) return aborted();
90
+ const classified = classifyGatewayError(error);
91
+ logger.warn("auth-gateway transcription failed", {
92
+ format: "transcriptions",
93
+ error: classified.message,
94
+ peer,
95
+ });
96
+ return transcriptions.formatError(classified.status, classified.type, classified.message);
97
+ }
98
+ }