feat: add gRPC-Web client service layer for OrchestratorService
- Create src/lib/services/orchestrator.ts with Connect-Web transport - Typed processRequest() async generator for server-streaming RPC - OrchestratorError class mapping gRPC status codes to app errors - Configurable endpoint with resetTransport() for reconfiguration Closes #4 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
89
src/lib/services/orchestrator.ts
Normal file
89
src/lib/services/orchestrator.ts
Normal file
@@ -0,0 +1,89 @@
|
||||
import { createClient } from '@connectrpc/connect';
|
||||
import { createGrpcWebTransport } from '@connectrpc/connect-web';
|
||||
import { OrchestratorService } from '$lib/proto/llm_multiverse/v1/orchestrator_pb';
|
||||
import type {
|
||||
ProcessRequestResponse,
|
||||
SessionConfig
|
||||
} from '$lib/proto/llm_multiverse/v1/orchestrator_pb';
|
||||
import { create } from '@bufbuild/protobuf';
|
||||
import { ProcessRequestRequestSchema } from '$lib/proto/llm_multiverse/v1/orchestrator_pb';
|
||||
|
||||
/**
|
||||
* Application-level error wrapping gRPC status codes.
|
||||
*/
|
||||
export class OrchestratorError extends Error {
|
||||
constructor(
|
||||
message: string,
|
||||
public readonly code: string,
|
||||
public readonly details?: string
|
||||
) {
|
||||
super(message);
|
||||
this.name = 'OrchestratorError';
|
||||
}
|
||||
}
|
||||
|
||||
const DEFAULT_ENDPOINT = '/';
|
||||
|
||||
let transport: ReturnType<typeof createGrpcWebTransport> | null = null;
|
||||
|
||||
function getTransport(endpoint?: string) {
|
||||
if (!transport) {
|
||||
transport = createGrpcWebTransport({
|
||||
baseUrl: endpoint ?? DEFAULT_ENDPOINT
|
||||
});
|
||||
}
|
||||
return transport;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the transport (useful for reconfiguring the endpoint).
|
||||
*/
|
||||
export function resetTransport(): void {
|
||||
transport = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a configured orchestrator client.
|
||||
*/
|
||||
function getClient(endpoint?: string) {
|
||||
return createClient(OrchestratorService, getTransport(endpoint));
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a request to the orchestrator and yield streaming responses.
|
||||
*
|
||||
* Returns an async iterator of `ProcessRequestResponse` messages,
|
||||
* each containing the current orchestration state, status message,
|
||||
* and optionally intermediate or final results.
|
||||
*/
|
||||
export async function* processRequest(
|
||||
sessionId: string,
|
||||
userMessage: string,
|
||||
sessionConfig?: SessionConfig,
|
||||
endpoint?: string
|
||||
): AsyncGenerator<ProcessRequestResponse> {
|
||||
const client = getClient(endpoint);
|
||||
|
||||
const request = create(ProcessRequestRequestSchema, {
|
||||
sessionId,
|
||||
userMessage,
|
||||
sessionConfig
|
||||
});
|
||||
|
||||
try {
|
||||
for await (const response of client.processRequest(request)) {
|
||||
yield response;
|
||||
}
|
||||
} catch (err: unknown) {
|
||||
if (err instanceof Error) {
|
||||
// ConnectError has a `code` property
|
||||
const code = 'code' in err ? (err as { code: unknown }).code : undefined;
|
||||
throw new OrchestratorError(
|
||||
err.message,
|
||||
typeof code === 'string' ? code : 'unknown',
|
||||
err.message
|
||||
);
|
||||
}
|
||||
throw new OrchestratorError('Unknown error', 'unknown');
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user