@xiaozhiclaw/provider-core-server 0.1.0 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,5 @@
1
1
  import type { ProviderRegistry } from "@xiaozhiclaw/provider-core";
2
- import type { ProviderCoreRequest } from "./request-normalizer.js";
2
+ import type { ProviderCoreMediaRequest, ProviderCoreRequest } from "./request-normalizer.js";
3
3
  export interface ResolvedCredential {
4
4
  apiKey: string;
5
5
  baseUrl?: string;
@@ -11,7 +11,7 @@ export interface ResolvedCredential {
11
11
  egressEndpoint?: string;
12
12
  }
13
13
  export interface CredentialResolveContext {
14
- request: ProviderCoreRequest;
14
+ request: ProviderCoreRequest | ProviderCoreMediaRequest;
15
15
  registry: Pick<ProviderRegistry, "resolveApiKey">;
16
16
  }
17
17
  export interface CredentialReport {
@@ -1,6 +1,8 @@
1
1
  export class EnvCredentialResolver {
2
2
  async resolve(context) {
3
3
  const apiKey = context.registry.resolveApiKey(context.request.provider);
4
+ if (!apiKey && isNoAuthProvider(context))
5
+ return { apiKey: "", source: "env" };
4
6
  if (!apiKey)
5
7
  throw new Error(`Missing API key for provider: ${context.request.provider}`);
6
8
  return { apiKey, source: "env" };
@@ -23,6 +25,8 @@ export class ExternalKeyPoolResolver {
23
25
  this.fallback = config.fallback ?? new EnvCredentialResolver();
24
26
  }
25
27
  async resolve(context) {
28
+ if (isNoAuthProvider(context))
29
+ return this.fallback.resolve(context);
26
30
  if (!this.baseUrl)
27
31
  return this.fallback.resolve(context);
28
32
  const controller = new AbortController();
@@ -103,6 +107,10 @@ export class ExternalKeyPoolResolver {
103
107
  });
104
108
  }
105
109
  }
110
+ function isNoAuthProvider(context) {
111
+ const registry = context.registry;
112
+ return registry.getProvider?.(context.request.provider)?.authType === "none";
113
+ }
106
114
  function normalizeEgressMode(value) {
107
115
  return value === "regional_proxy" ? "regional_proxy" : "direct";
108
116
  }
@@ -1,16 +1,25 @@
1
- import type { LLMRequest } from "@xiaozhiclaw/provider-core";
1
+ import type { LLMRequest, MediaRequest } from "@xiaozhiclaw/provider-core";
2
2
  export type PublicProtocol = "openai-chat" | "openai-responses" | "anthropic-messages";
3
+ export type ProviderCoreProtocol = PublicProtocol | "media";
4
+ export interface ProviderCoreMetadata {
5
+ [key: string]: unknown;
6
+ userId?: string;
7
+ workspaceId?: string;
8
+ sessionId?: string;
9
+ requestId?: string;
10
+ publicModel?: string;
11
+ source?: "qlogicagent" | "llmrouter";
12
+ }
3
13
  export interface ProviderCoreRequest extends LLMRequest {
4
14
  protocol: PublicProtocol;
5
15
  provider: string;
6
- metadata?: {
7
- userId?: string;
8
- workspaceId?: string;
9
- sessionId?: string;
10
- requestId?: string;
11
- publicModel?: string;
12
- source?: "qlogicagent" | "llmrouter";
13
- };
16
+ metadata?: ProviderCoreMetadata;
14
17
  providerOptions?: Record<string, unknown>;
15
18
  }
19
+ export interface ProviderCoreMediaRequest extends MediaRequest {
20
+ protocol: "media";
21
+ provider: string;
22
+ metadata?: ProviderCoreMetadata;
23
+ }
16
24
  export declare function normalizeProviderCoreRequest(body: unknown): ProviderCoreRequest;
25
+ export declare function normalizeProviderCoreMediaRequest(body: unknown): ProviderCoreMediaRequest;
@@ -14,3 +14,20 @@ export function normalizeProviderCoreRequest(body) {
14
14
  throw new Error("messages must be an array.");
15
15
  return candidate;
16
16
  }
17
+ export function normalizeProviderCoreMediaRequest(body) {
18
+ if (!body || typeof body !== "object") {
19
+ throw new Error("Request body must be an object.");
20
+ }
21
+ const candidate = body;
22
+ if (!candidate.provider)
23
+ throw new Error("provider is required.");
24
+ if (!candidate.model)
25
+ throw new Error("model is required.");
26
+ if (!candidate.mediaType)
27
+ throw new Error("mediaType is required.");
28
+ return {
29
+ ...candidate,
30
+ protocol: "media",
31
+ prompt: candidate.prompt ?? candidate.text ?? "",
32
+ };
33
+ }
package/dist/routes.d.ts CHANGED
@@ -1,12 +1,23 @@
1
1
  import type { IncomingMessage, ServerResponse } from "node:http";
2
- import { type ModelInfo, type ProviderDef, type ProviderEvent } from "@xiaozhiclaw/provider-core";
2
+ import { type MediaResult, type ModelInfo, type ProviderDef, type ProviderEvent } from "@xiaozhiclaw/provider-core";
3
3
  import type { ProviderCoreServerConfig } from "./config.js";
4
- import { type ProviderCoreRequest } from "./request-normalizer.js";
4
+ import { type ProviderCoreMediaRequest, type ProviderCoreRequest } from "./request-normalizer.js";
5
5
  export interface ProviderCoreRuntime {
6
6
  listProviders(): ProviderDef[];
7
7
  listModels(): Promise<ProviderCoreCatalogModel[]> | ProviderCoreCatalogModel[];
8
8
  invoke(request: ProviderCoreRequest, signal?: AbortSignal): Promise<ProviderEvent[]>;
9
9
  stream(request: ProviderCoreRequest, signal?: AbortSignal): AsyncIterable<ProviderEvent>;
10
+ generateMedia(request: ProviderCoreMediaRequest, signal?: AbortSignal): Promise<MediaResult>;
11
+ getMediaTaskStatus?(provider: string, taskId: string, signal?: AbortSignal): Promise<{
12
+ status: string;
13
+ task: Record<string, unknown>;
14
+ }>;
15
+ listMediaTasks?(provider: string, options?: {
16
+ after?: string;
17
+ limit?: number;
18
+ status?: string;
19
+ }, signal?: AbortSignal): Promise<Record<string, unknown>>;
20
+ deleteMediaTask?(provider: string, taskId: string, signal?: AbortSignal): Promise<void>;
10
21
  }
11
22
  export interface ProviderCoreCatalogModel extends ModelInfo {
12
23
  provider: string;
@@ -19,6 +30,7 @@ export declare class FixtureProviderCoreRuntime implements ProviderCoreRuntime {
19
30
  listModels(): ProviderCoreCatalogModel[];
20
31
  invoke(request: ProviderCoreRequest): Promise<ProviderEvent[]>;
21
32
  stream(request: ProviderCoreRequest): AsyncIterable<ProviderEvent>;
33
+ generateMedia(request: ProviderCoreMediaRequest): Promise<MediaResult>;
22
34
  }
23
35
  export declare function routeProviderCoreRequest(req: IncomingMessage, res: ServerResponse, config: ProviderCoreServerConfig, runtime: ProviderCoreRuntime): Promise<void>;
24
36
  export declare function flattenProviderModels(providers: ProviderDef[]): ProviderCoreCatalogModel[];
package/dist/routes.js CHANGED
@@ -2,7 +2,7 @@ import { BUILTIN_PROVIDERS } from "@xiaozhiclaw/provider-core";
2
2
  import { isAuthorized } from "./auth.js";
3
3
  import { healthPayload } from "./health.js";
4
4
  import { attachRequestId, createRequestContext, logRequest, logRequestComplete } from "./logging.js";
5
- import { normalizeProviderCoreRequest } from "./request-normalizer.js";
5
+ import { normalizeProviderCoreMediaRequest, normalizeProviderCoreRequest } from "./request-normalizer.js";
6
6
  import { startSse, writeProviderEvent } from "./sse.js";
7
7
  export class FixtureProviderCoreRuntime {
8
8
  listProviders() {
@@ -24,6 +24,16 @@ export class FixtureProviderCoreRuntime {
24
24
  yield { type: "usage.delta", usage: { promptTokens: request.messages.length, completionTokens: 4 } };
25
25
  yield { type: "response.completed", usage: { promptTokens: request.messages.length, completionTokens: 4 }, finishReason: "stop" };
26
26
  }
27
+ async generateMedia(request) {
28
+ return {
29
+ mediaUrls: [`https://provider-core.fixture/media/${request.model}`],
30
+ model: request.model,
31
+ size: request.size,
32
+ billingUnit: request.mediaType === "embedding" ? "per_token" : "per_call",
33
+ billingQuantity: request.mediaType === "embedding" ? 1 : (request.n ?? 1),
34
+ metadata: request.mediaType === "embedding" ? { embeddings: [[0.1, 0.2, 0.3]], dimensions: 3 } : undefined,
35
+ };
36
+ }
27
37
  }
28
38
  export async function routeProviderCoreRequest(req, res, config, runtime) {
29
39
  const context = createRequestContext(req);
@@ -68,6 +78,51 @@ export async function routeProviderCoreRequest(req, res, config, runtime) {
68
78
  logRequestComplete(req, context, 200);
69
79
  return;
70
80
  }
81
+ if (url.pathname === "/v1/media/generate" && req.method === "POST") {
82
+ const request = normalizeProviderCoreMediaRequest(await readJson(req));
83
+ sendJson(res, 200, { result: await runtime.generateMedia(request) });
84
+ logRequestComplete(req, context, 200);
85
+ return;
86
+ }
87
+ if (url.pathname === "/v1/media/tasks" && req.method === "GET") {
88
+ const provider = url.searchParams.get("provider") ?? "";
89
+ if (!provider || !runtime.listMediaTasks) {
90
+ sendJson(res, 400, { error: { message: "provider is required or task listing is unsupported" } });
91
+ logRequestComplete(req, context, 400);
92
+ return;
93
+ }
94
+ sendJson(res, 200, await runtime.listMediaTasks(provider, {
95
+ after: url.searchParams.get("after") ?? undefined,
96
+ limit: Number(url.searchParams.get("limit") ?? "") || undefined,
97
+ status: url.searchParams.get("status") ?? undefined,
98
+ }));
99
+ logRequestComplete(req, context, 200);
100
+ return;
101
+ }
102
+ const taskMatch = url.pathname.match(/^\/v1\/media\/tasks\/([^/]+)$/);
103
+ if (taskMatch && req.method === "GET") {
104
+ const provider = url.searchParams.get("provider") ?? "";
105
+ if (!provider || !runtime.getMediaTaskStatus) {
106
+ sendJson(res, 400, { error: { message: "provider is required or task status is unsupported" } });
107
+ logRequestComplete(req, context, 400);
108
+ return;
109
+ }
110
+ sendJson(res, 200, await runtime.getMediaTaskStatus(provider, decodeURIComponent(taskMatch[1])));
111
+ logRequestComplete(req, context, 200);
112
+ return;
113
+ }
114
+ if (taskMatch && req.method === "DELETE") {
115
+ const provider = url.searchParams.get("provider") ?? "";
116
+ if (!provider || !runtime.deleteMediaTask) {
117
+ sendJson(res, 400, { error: { message: "provider is required or task deletion is unsupported" } });
118
+ logRequestComplete(req, context, 400);
119
+ return;
120
+ }
121
+ await runtime.deleteMediaTask(provider, decodeURIComponent(taskMatch[1]));
122
+ sendJson(res, 200, { ok: true });
123
+ logRequestComplete(req, context, 200);
124
+ return;
125
+ }
71
126
  sendJson(res, 404, { error: { message: "Not found" } });
72
127
  logRequestComplete(req, context, 404);
73
128
  }
package/dist/runtime.d.ts CHANGED
@@ -1,7 +1,7 @@
1
- import { type ProviderDef, type ProviderEvent } from "@xiaozhiclaw/provider-core";
1
+ import { type MediaResult, type ProviderDef, type ProviderEvent } from "@xiaozhiclaw/provider-core";
2
2
  import type { ProviderCoreServerConfig } from "./config.js";
3
3
  import { type CredentialResolver } from "./credential-resolver.js";
4
- import type { ProviderCoreRequest } from "./request-normalizer.js";
4
+ import type { ProviderCoreMediaRequest, ProviderCoreRequest } from "./request-normalizer.js";
5
5
  import { type ProviderCoreCatalogModel, type ProviderCoreRuntime } from "./routes.js";
6
6
  export declare class RealProviderCoreRuntime implements ProviderCoreRuntime {
7
7
  private registry;
@@ -11,5 +11,18 @@ export declare class RealProviderCoreRuntime implements ProviderCoreRuntime {
11
11
  listModels(): Promise<ProviderCoreCatalogModel[]>;
12
12
  invoke(request: ProviderCoreRequest, signal?: AbortSignal): Promise<ProviderEvent[]>;
13
13
  stream(request: ProviderCoreRequest, signal?: AbortSignal): AsyncIterable<ProviderEvent>;
14
+ generateMedia(request: ProviderCoreMediaRequest, signal?: AbortSignal): Promise<MediaResult>;
15
+ getMediaTaskStatus(provider: string, taskId: string, signal?: AbortSignal): Promise<{
16
+ status: string;
17
+ task: Record<string, unknown>;
18
+ }>;
19
+ listMediaTasks(provider: string, options?: {
20
+ after?: string;
21
+ limit?: number;
22
+ status?: string;
23
+ }, signal?: AbortSignal): Promise<Record<string, unknown>>;
24
+ deleteMediaTask(provider: string, taskId: string, signal?: AbortSignal): Promise<void>;
14
25
  private reportCredential;
26
+ private resolveProviderCredential;
27
+ private mediaTransportForProvider;
15
28
  }
package/dist/runtime.js CHANGED
@@ -1,4 +1,4 @@
1
- import { createLLMClient, ProviderRegistry, } from "@xiaozhiclaw/provider-core";
1
+ import { createLLMClient, isAsyncMediaTransport, MediaClient, ProviderRegistry, } from "@xiaozhiclaw/provider-core";
2
2
  import { EnvCredentialResolver, ExternalKeyPoolResolver } from "./credential-resolver.js";
3
3
  import { flattenProviderModels } from "./routes.js";
4
4
  export class RealProviderCoreRuntime {
@@ -94,6 +94,62 @@ export class RealProviderCoreRuntime {
94
94
  throw error;
95
95
  }
96
96
  }
97
+ async generateMedia(request, signal) {
98
+ const providerDef = this.registry.getProvider(request.provider);
99
+ if (!providerDef)
100
+ throw new Error(`Unknown provider: ${request.provider}`);
101
+ const started = Date.now();
102
+ let credential;
103
+ try {
104
+ credential = await this.credentialResolver.resolve({ request, registry: this.registry });
105
+ if (credential.baseUrl)
106
+ this.registry.applyOverride(request.provider, { baseUrl: credential.baseUrl });
107
+ const mediaClient = new MediaClient({ registry: this.registry });
108
+ const transport = mediaClient.getTransport(request.provider);
109
+ if (!transport)
110
+ throw new Error(`Provider does not support media generation: ${request.provider}`);
111
+ const result = await transport.generate(request, credential.apiKey, signal);
112
+ await this.reportCredential(credential, {
113
+ success: true,
114
+ latencyMs: Date.now() - started,
115
+ promptTokens: result.billingUnit === "per_token" ? Number(result.billingQuantity ?? 0) : undefined,
116
+ });
117
+ return result;
118
+ }
119
+ catch (error) {
120
+ if (credential) {
121
+ await this.reportCredential(credential, {
122
+ success: false,
123
+ statusCode: statusCodeFromError(error),
124
+ errorType: error instanceof Error ? error.name : "provider_error",
125
+ errorMessage: error instanceof Error ? error.message : String(error),
126
+ latencyMs: Date.now() - started,
127
+ });
128
+ }
129
+ throw error;
130
+ }
131
+ }
132
+ async getMediaTaskStatus(provider, taskId, signal) {
133
+ const credential = await this.resolveProviderCredential(provider, taskId);
134
+ const transport = this.mediaTransportForProvider(provider, credential.baseUrl);
135
+ if (!isAsyncMediaTransport(transport) || !transport.getTaskStatus)
136
+ throw new Error(`Provider does not support media task status: ${provider}`);
137
+ return transport.getTaskStatus(taskId, credential.apiKey, signal);
138
+ }
139
+ async listMediaTasks(provider, options, signal) {
140
+ const credential = await this.resolveProviderCredential(provider, "media-tasks");
141
+ const transport = this.mediaTransportForProvider(provider, credential.baseUrl);
142
+ if (!isAsyncMediaTransport(transport))
143
+ throw new Error(`Provider does not support media task listing: ${provider}`);
144
+ return transport.listVideoTasks(credential.apiKey, options, signal);
145
+ }
146
+ async deleteMediaTask(provider, taskId, signal) {
147
+ const credential = await this.resolveProviderCredential(provider, taskId);
148
+ const transport = this.mediaTransportForProvider(provider, credential.baseUrl);
149
+ if (!isAsyncMediaTransport(transport))
150
+ throw new Error(`Provider does not support media task deletion: ${provider}`);
151
+ await transport.deleteVideoTask(taskId, credential.apiKey, signal);
152
+ }
97
153
  async reportCredential(credential, report) {
98
154
  try {
99
155
  await this.credentialResolver.report?.(credential, report);
@@ -102,6 +158,26 @@ export class RealProviderCoreRuntime {
102
158
  // Credential reporting must never mask a successful model response.
103
159
  }
104
160
  }
161
+ async resolveProviderCredential(provider, model) {
162
+ return this.credentialResolver.resolve({
163
+ request: {
164
+ protocol: "media",
165
+ provider,
166
+ model,
167
+ mediaType: "video",
168
+ prompt: "",
169
+ },
170
+ registry: this.registry,
171
+ });
172
+ }
173
+ mediaTransportForProvider(provider, baseUrl) {
174
+ if (baseUrl)
175
+ this.registry.applyOverride(provider, { baseUrl });
176
+ const transport = new MediaClient({ registry: this.registry }).getTransport(provider);
177
+ if (!transport)
178
+ throw new Error(`Provider does not support media generation: ${provider}`);
179
+ return transport;
180
+ }
105
181
  }
106
182
  function createCredentialResolver(config) {
107
183
  if (!config || config.credentialResolver === "env")
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xiaozhiclaw/provider-core-server",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "Internal HTTP/SSE server for QLogic Provider Core",
5
5
  "type": "module",
6
6
  "main": "./dist/server.js",
@@ -15,7 +15,7 @@
15
15
  "node": ">=22.0.0"
16
16
  },
17
17
  "dependencies": {
18
- "@xiaozhiclaw/provider-core": "^0.1.0"
18
+ "@xiaozhiclaw/provider-core": "^0.1.2"
19
19
  },
20
20
  "scripts": {
21
21
  "build": "tsc -p tsconfig.json",