@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.
- package/CHANGELOG.md +16 -0
- package/dist/types/auth-gateway/dispatch.d.ts +80 -0
- package/dist/types/auth-gateway/http.d.ts +5 -6
- package/dist/types/auth-gateway/index.d.ts +1 -0
- package/dist/types/auth-gateway/routes/embeddings.d.ts +3 -0
- package/dist/types/auth-gateway/routes/images.d.ts +3 -0
- package/dist/types/auth-gateway/routes/rerank.d.ts +3 -0
- package/dist/types/auth-gateway/routes/speech.d.ts +2 -0
- package/dist/types/auth-gateway/routes/systemone.d.ts +2 -0
- package/dist/types/auth-gateway/routes/transcriptions.d.ts +3 -0
- package/dist/types/auth-gateway/routes/video.d.ts +7 -0
- package/dist/types/auth-gateway/server.d.ts +10 -16
- package/dist/types/embeddings/index.d.ts +7 -0
- package/dist/types/embeddings/openai-embeddings.d.ts +15 -0
- package/dist/types/embeddings/types.d.ts +15 -0
- package/dist/types/images/google-antigravity.d.ts +9 -0
- package/dist/types/images/google-generative-ai.d.ts +3 -0
- package/dist/types/images/index.d.ts +14 -0
- package/dist/types/images/openai-hosted.d.ts +3 -0
- package/dist/types/images/openai-images.d.ts +5 -0
- package/dist/types/images/openrouter-images.d.ts +3 -0
- package/dist/types/images/shared.d.ts +34 -0
- package/dist/types/images/types.d.ts +31 -0
- package/dist/types/index.d.ts +7 -1
- package/dist/types/judgment/typesafe.d.ts +2 -0
- package/dist/types/providers/embeddings-server.d.ts +32 -0
- package/dist/types/providers/images-server.d.ts +22 -0
- package/dist/types/providers/rerank-server.d.ts +35 -0
- package/dist/types/providers/speech-server.d.ts +8 -0
- package/dist/types/providers/systemone-server.d.ts +26 -0
- package/dist/types/providers/transcriptions-server.d.ts +32 -0
- package/dist/types/providers/video-server.d.ts +39 -0
- package/dist/types/rerank/index.d.ts +7 -0
- package/dist/types/rerank/openrouter-rerank.d.ts +15 -0
- package/dist/types/rerank/types.d.ts +17 -0
- package/dist/types/speech/index.d.ts +13 -0
- package/dist/types/speech/openai-speech.d.ts +3 -0
- package/dist/types/speech/transport.d.ts +7 -0
- package/dist/types/speech/types.d.ts +24 -0
- package/dist/types/speech/xai-tts.d.ts +7 -0
- package/dist/types/transcription/index.d.ts +7 -0
- package/dist/types/transcription/openai-transcriptions.d.ts +15 -0
- package/dist/types/transcription/types.d.ts +41 -0
- package/dist/types/video/index.d.ts +11 -0
- package/dist/types/video/openrouter-video.d.ts +19 -0
- package/dist/types/video/types.d.ts +62 -0
- package/package.json +30 -6
- package/src/auth-gateway/dispatch.ts +273 -0
- package/src/auth-gateway/http.ts +6 -7
- package/src/auth-gateway/index.ts +1 -0
- package/src/auth-gateway/routes/embeddings.ts +98 -0
- package/src/auth-gateway/routes/images.ts +131 -0
- package/src/auth-gateway/routes/rerank.ts +87 -0
- package/src/auth-gateway/routes/speech.ts +101 -0
- package/src/auth-gateway/routes/systemone.ts +116 -0
- package/src/auth-gateway/routes/transcriptions.ts +98 -0
- package/src/auth-gateway/routes/video.ts +243 -0
- package/src/auth-gateway/server.ts +123 -260
- package/src/embeddings/index.ts +17 -0
- package/src/embeddings/openai-embeddings.ts +141 -0
- package/src/embeddings/types.ts +14 -0
- package/src/error/rate-limit.ts +1 -1
- package/src/images/google-antigravity.ts +180 -0
- package/src/images/google-generative-ai.ts +92 -0
- package/src/images/index.ts +59 -0
- package/src/images/openai-hosted.ts +185 -0
- package/src/images/openai-images.ts +110 -0
- package/src/images/openrouter-images.ts +33 -0
- package/src/images/shared.ts +193 -0
- package/src/images/types.ts +36 -0
- package/src/index.ts +7 -1
- package/src/judgment/typesafe.ts +5 -0
- package/src/providers/embeddings-server.ts +151 -0
- package/src/providers/images-server.ts +159 -0
- package/src/providers/rerank-server.ts +166 -0
- package/src/providers/speech-server.ts +53 -0
- package/src/providers/systemone-server.ts +73 -0
- package/src/providers/transcriptions-server.ts +243 -0
- package/src/providers/video-server.ts +286 -0
- package/src/rerank/index.ts +13 -0
- package/src/rerank/openrouter-rerank.ts +136 -0
- package/src/rerank/types.ts +20 -0
- package/src/speech/index.ts +35 -0
- package/src/speech/openai-speech.ts +26 -0
- package/src/speech/transport.ts +66 -0
- package/src/speech/types.ts +37 -0
- package/src/speech/xai-tts.ts +41 -0
- package/src/transcription/index.ts +17 -0
- package/src/transcription/openai-transcriptions.ts +133 -0
- package/src/transcription/types.ts +46 -0
- package/src/video/index.ts +34 -0
- package/src/video/openrouter-video.ts +210 -0
- package/src/video/types.ts +72 -0
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
import type { Api, Model } from "@oh-my-pi/pi-catalog/types";
|
|
2
|
+
import { logger } from "@oh-my-pi/pi-utils";
|
|
3
|
+
import { classifyGatewayError } from "../../error/gateway";
|
|
4
|
+
import * as videoServer from "../../providers/video-server";
|
|
5
|
+
import { downloadVideo, pollVideo, submitVideo } from "../../video";
|
|
6
|
+
import type { VideoJob } from "../../video/types";
|
|
7
|
+
import { deterministicUuid } from "../../utils/deterministic-id";
|
|
8
|
+
import {
|
|
9
|
+
type AuthGatewayBootOptions,
|
|
10
|
+
buildGatewayApiKeyResolver,
|
|
11
|
+
mirrorRequestAbort,
|
|
12
|
+
resolveGatewayApiKey,
|
|
13
|
+
} from "../dispatch";
|
|
14
|
+
import { gatewayResponseHeaders, json, resolveClientIdentity } from "../http";
|
|
15
|
+
|
|
16
|
+
interface ResolvedVideoRequest {
|
|
17
|
+
model: Model<Api>;
|
|
18
|
+
upstreamId: string;
|
|
19
|
+
sessionId: string;
|
|
20
|
+
apiKey: string;
|
|
21
|
+
controller: AbortController;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function aborted(): Response {
|
|
25
|
+
return videoServer.formatError(499, "request_aborted", "client closed request");
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async function resolveVideoJob(
|
|
29
|
+
bootOpts: AuthGatewayBootOptions,
|
|
30
|
+
req: Request,
|
|
31
|
+
peer: string,
|
|
32
|
+
gatewayId: string,
|
|
33
|
+
): Promise<ResolvedVideoRequest | Response> {
|
|
34
|
+
const controller = mirrorRequestAbort(req);
|
|
35
|
+
if (controller.signal.aborted) return aborted();
|
|
36
|
+
let identity: videoServer.GatewayJobIdentity;
|
|
37
|
+
try {
|
|
38
|
+
identity = videoServer.decodeGatewayJobId(gatewayId);
|
|
39
|
+
} catch (error) {
|
|
40
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
41
|
+
return videoServer.formatError(400, "invalid_request_error", message);
|
|
42
|
+
}
|
|
43
|
+
const model = bootOpts.resolveModel(`${identity.provider}/${identity.modelId}`);
|
|
44
|
+
if (!model) {
|
|
45
|
+
return videoServer.formatError(
|
|
46
|
+
404,
|
|
47
|
+
"invalid_request_error",
|
|
48
|
+
`Unknown model: ${identity.provider}/${identity.modelId}`,
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
if (model.api !== "openrouter-video") {
|
|
52
|
+
return videoServer.formatError(
|
|
53
|
+
400,
|
|
54
|
+
"invalid_request_error",
|
|
55
|
+
`Model ${model.id} does not support video generation`,
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
const sessionId = deterministicUuid(`video\u0000${model.provider}/${model.id}`);
|
|
59
|
+
const apiKey = await resolveGatewayApiKey(bootOpts.storage, model, sessionId, controller.signal, peer);
|
|
60
|
+
if (controller.signal.aborted) return aborted();
|
|
61
|
+
if (typeof apiKey !== "string") return videoServer.formatError(apiKey.status, apiKey.type, apiKey.message);
|
|
62
|
+
return { model, upstreamId: identity.upstreamId, sessionId, apiKey, controller };
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function videoOptions(bootOpts: AuthGatewayBootOptions, resolved: ResolvedVideoRequest, peer: string) {
|
|
66
|
+
return {
|
|
67
|
+
apiKey: buildGatewayApiKeyResolver(
|
|
68
|
+
bootOpts.storage,
|
|
69
|
+
resolved.model,
|
|
70
|
+
resolved.sessionId,
|
|
71
|
+
resolved.apiKey,
|
|
72
|
+
resolved.controller.signal,
|
|
73
|
+
"video",
|
|
74
|
+
peer,
|
|
75
|
+
),
|
|
76
|
+
fetch: bootOpts.fetch,
|
|
77
|
+
signal: resolved.controller.signal,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function logVideoRequest(
|
|
82
|
+
requestId: string,
|
|
83
|
+
operation: "submit" | "poll" | "content",
|
|
84
|
+
model: ResolvedVideoRequest["model"],
|
|
85
|
+
peer: string,
|
|
86
|
+
): void {
|
|
87
|
+
logger.info("auth-gateway request", {
|
|
88
|
+
requestId,
|
|
89
|
+
format: `video-${operation}`,
|
|
90
|
+
model: model.id,
|
|
91
|
+
resolvedProvider: model.provider,
|
|
92
|
+
resolvedModel: model.id,
|
|
93
|
+
stream: operation === "content",
|
|
94
|
+
peer,
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function recordCompletedUsage(
|
|
99
|
+
bootOpts: AuthGatewayBootOptions,
|
|
100
|
+
resolved: ResolvedVideoRequest,
|
|
101
|
+
req: Request,
|
|
102
|
+
job: VideoJob,
|
|
103
|
+
): void {
|
|
104
|
+
if (job.status !== "completed" || job.usage === undefined) return;
|
|
105
|
+
bootOpts.storage.recordObservedUsage({
|
|
106
|
+
provider: resolved.model.provider,
|
|
107
|
+
model: resolved.model.id,
|
|
108
|
+
usage: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
109
|
+
costUsd: job.usage.cost.total,
|
|
110
|
+
client: resolveClientIdentity(req.headers),
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** OpenRouter-compatible `POST /v1/videos` asynchronous video submit handler. */
|
|
115
|
+
export async function handleVideoSubmit(
|
|
116
|
+
bootOpts: AuthGatewayBootOptions,
|
|
117
|
+
req: Request,
|
|
118
|
+
peer: string,
|
|
119
|
+
): Promise<Response> {
|
|
120
|
+
const startedAt = performance.now();
|
|
121
|
+
const requestId = crypto.randomUUID();
|
|
122
|
+
const controller = mirrorRequestAbort(req);
|
|
123
|
+
if (controller.signal.aborted) return aborted();
|
|
124
|
+
let parsed: videoServer.VideoParsedRequest;
|
|
125
|
+
try {
|
|
126
|
+
parsed = videoServer.parseRequest(await req.json());
|
|
127
|
+
} catch (error) {
|
|
128
|
+
if (controller.signal.aborted) return aborted();
|
|
129
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
130
|
+
return videoServer.formatError(400, "invalid_request_error", message);
|
|
131
|
+
}
|
|
132
|
+
const model = bootOpts.resolveModel(parsed.modelId);
|
|
133
|
+
if (!model) return videoServer.formatError(404, "invalid_request_error", `Unknown model: ${parsed.modelId}`);
|
|
134
|
+
if (model.api !== "openrouter-video") {
|
|
135
|
+
return videoServer.formatError(
|
|
136
|
+
400,
|
|
137
|
+
"invalid_request_error",
|
|
138
|
+
`Model ${parsed.modelId} does not support video generation`,
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
const sessionId = deterministicUuid(`video\u0000${model.provider}/${model.id}`);
|
|
142
|
+
const apiKey = await resolveGatewayApiKey(bootOpts.storage, model, sessionId, controller.signal, peer);
|
|
143
|
+
if (controller.signal.aborted) return aborted();
|
|
144
|
+
if (typeof apiKey !== "string") return videoServer.formatError(apiKey.status, apiKey.type, apiKey.message);
|
|
145
|
+
logger.info("auth-gateway request", {
|
|
146
|
+
requestId,
|
|
147
|
+
format: "video-submit",
|
|
148
|
+
model: parsed.modelId,
|
|
149
|
+
resolvedProvider: model.provider,
|
|
150
|
+
resolvedModel: model.id,
|
|
151
|
+
stream: false,
|
|
152
|
+
peer,
|
|
153
|
+
});
|
|
154
|
+
try {
|
|
155
|
+
const job = await submitVideo(model, parsed.request, {
|
|
156
|
+
apiKey: buildGatewayApiKeyResolver(
|
|
157
|
+
bootOpts.storage,
|
|
158
|
+
model,
|
|
159
|
+
sessionId,
|
|
160
|
+
apiKey,
|
|
161
|
+
controller.signal,
|
|
162
|
+
"video",
|
|
163
|
+
peer,
|
|
164
|
+
),
|
|
165
|
+
fetch: bootOpts.fetch,
|
|
166
|
+
signal: controller.signal,
|
|
167
|
+
});
|
|
168
|
+
const gatewayId = videoServer.encodeGatewayJobId({
|
|
169
|
+
provider: model.provider,
|
|
170
|
+
modelId: model.id,
|
|
171
|
+
upstreamId: job.id,
|
|
172
|
+
});
|
|
173
|
+
return json(
|
|
174
|
+
202,
|
|
175
|
+
videoServer.encodeSubmitResponse(job, req, gatewayId),
|
|
176
|
+
gatewayResponseHeaders(model, { requestId, startedAt }),
|
|
177
|
+
);
|
|
178
|
+
} catch (error) {
|
|
179
|
+
if (controller.signal.aborted) return aborted();
|
|
180
|
+
const classified = classifyGatewayError(error);
|
|
181
|
+
logger.warn("auth-gateway video submit failed", { format: "video-submit", error: classified.message, peer });
|
|
182
|
+
return videoServer.formatError(classified.status, classified.type, classified.message);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/** OpenRouter-compatible `GET /v1/videos/:id` asynchronous video poll handler. */
|
|
187
|
+
export async function handleVideoPoll(
|
|
188
|
+
bootOpts: AuthGatewayBootOptions,
|
|
189
|
+
req: Request,
|
|
190
|
+
peer: string,
|
|
191
|
+
gatewayId: string,
|
|
192
|
+
): Promise<Response> {
|
|
193
|
+
const startedAt = performance.now();
|
|
194
|
+
const requestId = crypto.randomUUID();
|
|
195
|
+
const resolved = await resolveVideoJob(bootOpts, req, peer, gatewayId);
|
|
196
|
+
if (resolved instanceof Response) return resolved;
|
|
197
|
+
logVideoRequest(requestId, "poll", resolved.model, peer);
|
|
198
|
+
try {
|
|
199
|
+
const job = await pollVideo(resolved.model, resolved.upstreamId, videoOptions(bootOpts, resolved, peer));
|
|
200
|
+
recordCompletedUsage(bootOpts, resolved, req, job);
|
|
201
|
+
return json(
|
|
202
|
+
200,
|
|
203
|
+
videoServer.encodePollResponse(job, req, gatewayId),
|
|
204
|
+
gatewayResponseHeaders(resolved.model, {
|
|
205
|
+
requestId,
|
|
206
|
+
...(job.usage !== undefined && { costUsd: job.usage.cost.total }),
|
|
207
|
+
startedAt,
|
|
208
|
+
}),
|
|
209
|
+
);
|
|
210
|
+
} catch (error) {
|
|
211
|
+
if (resolved.controller.signal.aborted) return aborted();
|
|
212
|
+
const classified = classifyGatewayError(error);
|
|
213
|
+
logger.warn("auth-gateway video poll failed", { format: "video-poll", error: classified.message, peer });
|
|
214
|
+
return videoServer.formatError(classified.status, classified.type, classified.message);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/** OpenRouter-compatible `GET /v1/videos/:id/content` streaming video content handler. */
|
|
219
|
+
export async function handleVideoContent(
|
|
220
|
+
bootOpts: AuthGatewayBootOptions,
|
|
221
|
+
req: Request,
|
|
222
|
+
peer: string,
|
|
223
|
+
gatewayId: string,
|
|
224
|
+
): Promise<Response> {
|
|
225
|
+
const startedAt = performance.now();
|
|
226
|
+
const requestId = crypto.randomUUID();
|
|
227
|
+
const resolved = await resolveVideoJob(bootOpts, req, peer, gatewayId);
|
|
228
|
+
if (resolved instanceof Response) return resolved;
|
|
229
|
+
logVideoRequest(requestId, "content", resolved.model, peer);
|
|
230
|
+
try {
|
|
231
|
+
const content = await downloadVideo(resolved.model, resolved.upstreamId, videoOptions(bootOpts, resolved, peer));
|
|
232
|
+
const headers = new Headers(gatewayResponseHeaders(resolved.model, { requestId, startedAt }));
|
|
233
|
+
headers.set("Content-Type", content.contentType);
|
|
234
|
+
headers.set("X-Content-Type-Options", "nosniff");
|
|
235
|
+
if (content.contentLength !== undefined) headers.set("Content-Length", String(content.contentLength));
|
|
236
|
+
return new Response(content.body, { status: 200, headers });
|
|
237
|
+
} catch (error) {
|
|
238
|
+
if (resolved.controller.signal.aborted) return aborted();
|
|
239
|
+
const classified = classifyGatewayError(error);
|
|
240
|
+
logger.warn("auth-gateway video content failed", { format: "video-content", error: classified.message, peer });
|
|
241
|
+
return videoServer.formatError(classified.status, classified.type, classified.message);
|
|
242
|
+
}
|
|
243
|
+
}
|