@lobu/gateway 3.0.7 → 3.0.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 (69) hide show
  1. package/dist/auth/bedrock/models.d.ts +13 -0
  2. package/dist/auth/bedrock/models.d.ts.map +1 -0
  3. package/dist/auth/bedrock/models.js +75 -0
  4. package/dist/auth/bedrock/models.js.map +1 -0
  5. package/dist/auth/bedrock/provider-module.d.ts +17 -0
  6. package/dist/auth/bedrock/provider-module.d.ts.map +1 -0
  7. package/dist/auth/bedrock/provider-module.js +80 -0
  8. package/dist/auth/bedrock/provider-module.js.map +1 -0
  9. package/dist/auth/external/device-code-client.d.ts +2 -0
  10. package/dist/auth/external/device-code-client.d.ts.map +1 -1
  11. package/dist/auth/external/device-code-client.js +12 -4
  12. package/dist/auth/external/device-code-client.js.map +1 -1
  13. package/dist/auth/mcp/config-service.d.ts +3 -4
  14. package/dist/auth/mcp/config-service.d.ts.map +1 -1
  15. package/dist/auth/mcp/config-service.js +40 -12
  16. package/dist/auth/mcp/config-service.js.map +1 -1
  17. package/dist/auth/mcp/proxy.d.ts +1 -3
  18. package/dist/auth/mcp/proxy.d.ts.map +1 -1
  19. package/dist/auth/mcp/proxy.js.map +1 -1
  20. package/dist/cli/gateway.d.ts.map +1 -1
  21. package/dist/cli/gateway.js +12 -5
  22. package/dist/cli/gateway.js.map +1 -1
  23. package/dist/gateway/index.d.ts.map +1 -1
  24. package/dist/gateway/index.js +1 -3
  25. package/dist/gateway/index.js.map +1 -1
  26. package/dist/routes/internal/device-auth.d.ts +7 -0
  27. package/dist/routes/internal/device-auth.d.ts.map +1 -1
  28. package/dist/routes/internal/device-auth.js +101 -48
  29. package/dist/routes/internal/device-auth.js.map +1 -1
  30. package/dist/routes/public/cli-auth.d.ts.map +1 -1
  31. package/dist/routes/public/cli-auth.js +10 -0
  32. package/dist/routes/public/cli-auth.js.map +1 -1
  33. package/dist/services/bedrock-anthropic-service.d.ts +87 -0
  34. package/dist/services/bedrock-anthropic-service.d.ts.map +1 -0
  35. package/dist/services/bedrock-anthropic-service.js +453 -0
  36. package/dist/services/bedrock-anthropic-service.js.map +1 -0
  37. package/dist/services/bedrock-model-catalog.d.ts +28 -0
  38. package/dist/services/bedrock-model-catalog.d.ts.map +1 -0
  39. package/dist/services/bedrock-model-catalog.js +160 -0
  40. package/dist/services/bedrock-model-catalog.js.map +1 -0
  41. package/dist/services/bedrock-openai-service.d.ts +119 -0
  42. package/dist/services/bedrock-openai-service.d.ts.map +1 -0
  43. package/dist/services/bedrock-openai-service.js +412 -0
  44. package/dist/services/bedrock-openai-service.js.map +1 -0
  45. package/dist/services/core-services.d.ts +3 -0
  46. package/dist/services/core-services.d.ts.map +1 -1
  47. package/dist/services/core-services.js +13 -0
  48. package/dist/services/core-services.js.map +1 -1
  49. package/dist/services/system-config-resolver.d.ts.map +1 -1
  50. package/dist/services/system-config-resolver.js +0 -2
  51. package/dist/services/system-config-resolver.js.map +1 -1
  52. package/package.json +3 -1
  53. package/src/__tests__/bedrock-model-catalog.test.ts +40 -0
  54. package/src/__tests__/bedrock-openai-service.test.ts +157 -0
  55. package/src/__tests__/bedrock-provider-module.test.ts +56 -0
  56. package/src/__tests__/mcp-config-service.test.ts +1 -1
  57. package/src/__tests__/mcp-proxy.test.ts +1 -3
  58. package/src/auth/bedrock/provider-module.ts +110 -0
  59. package/src/auth/external/device-code-client.ts +14 -4
  60. package/src/auth/mcp/config-service.ts +49 -21
  61. package/src/auth/mcp/proxy.ts +1 -3
  62. package/src/cli/gateway.ts +8 -0
  63. package/src/gateway/index.ts +1 -3
  64. package/src/routes/internal/device-auth.ts +137 -51
  65. package/src/routes/public/cli-auth.ts +13 -0
  66. package/src/services/bedrock-model-catalog.ts +217 -0
  67. package/src/services/bedrock-openai-service.ts +658 -0
  68. package/src/services/core-services.ts +19 -0
  69. package/src/services/system-config-resolver.ts +0 -1
@@ -0,0 +1,658 @@
1
+ import { getModel } from "@mariozechner/pi-ai";
2
+ import { streamBedrock } from "@mariozechner/pi-ai/dist/providers/amazon-bedrock.js";
3
+ import type { Model } from "@mariozechner/pi-ai/dist/types.js";
4
+ import type { Context } from "hono";
5
+ import { Hono } from "hono";
6
+ import { createLogger } from "@lobu/core";
7
+ import {
8
+ BedrockModelCatalog,
9
+ buildDynamicBedrockModel,
10
+ resolveAwsRegion,
11
+ } from "./bedrock-model-catalog";
12
+
13
+ const logger = createLogger("bedrock-openai-service");
14
+
15
+ type BedrockStreamEvent = {
16
+ type: string;
17
+ contentIndex?: number;
18
+ delta?: string;
19
+ toolCall?: { id: string; name: string };
20
+ reason?: string;
21
+ message?: {
22
+ usage?: {
23
+ input?: number;
24
+ output?: number;
25
+ cacheRead?: number;
26
+ cacheWrite?: number;
27
+ totalTokens?: number;
28
+ };
29
+ };
30
+ error?: { errorMessage?: string };
31
+ };
32
+
33
+ interface OpenAITextPart {
34
+ type: "text";
35
+ text: string;
36
+ }
37
+
38
+ interface OpenAIImagePart {
39
+ type: "image_url";
40
+ image_url?: {
41
+ url?: string;
42
+ };
43
+ }
44
+
45
+ type OpenAIMessageContent = string | Array<OpenAITextPart | OpenAIImagePart>;
46
+
47
+ interface OpenAIChatMessage {
48
+ role: "system" | "developer" | "user" | "assistant" | "tool";
49
+ content?: OpenAIMessageContent | null;
50
+ tool_call_id?: string;
51
+ tool_calls?: Array<{
52
+ id?: string;
53
+ type?: "function";
54
+ function?: {
55
+ name?: string;
56
+ arguments?: string;
57
+ };
58
+ }>;
59
+ }
60
+
61
+ interface OpenAIChatTool {
62
+ type?: "function";
63
+ function?: {
64
+ name?: string;
65
+ description?: string;
66
+ parameters?: Record<string, unknown>;
67
+ };
68
+ }
69
+
70
+ interface OpenAIChatCompletionRequest {
71
+ model: string;
72
+ messages: OpenAIChatMessage[];
73
+ tools?: OpenAIChatTool[];
74
+ tool_choice?:
75
+ | "none"
76
+ | "auto"
77
+ | "required"
78
+ | {
79
+ type?: "function";
80
+ function?: {
81
+ name?: string;
82
+ };
83
+ };
84
+ max_tokens?: number;
85
+ max_completion_tokens?: number;
86
+ temperature?: number;
87
+ stop?: string | string[];
88
+ stream?: boolean;
89
+ stream_options?: {
90
+ include_usage?: boolean;
91
+ };
92
+ }
93
+
94
+ type PiMessage =
95
+ | {
96
+ role: "user";
97
+ content: Array<
98
+ | { type: "text"; text: string }
99
+ | { type: "image"; mimeType: string; data: string }
100
+ >;
101
+ }
102
+ | {
103
+ role: "assistant";
104
+ content: Array<
105
+ | { type: "text"; text: string }
106
+ | {
107
+ type: "toolCall";
108
+ id: string;
109
+ name: string;
110
+ arguments: Record<string, unknown>;
111
+ }
112
+ >;
113
+ }
114
+ | {
115
+ role: "toolResult";
116
+ toolCallId: string;
117
+ content: Array<
118
+ | { type: "text"; text: string }
119
+ | { type: "image"; mimeType: string; data: string }
120
+ >;
121
+ isError?: boolean;
122
+ };
123
+
124
+ interface BedrockContextPayload {
125
+ messages: PiMessage[];
126
+ systemPrompt?: string;
127
+ tools?: Array<{
128
+ name: string;
129
+ description?: string;
130
+ parameters: Record<string, unknown>;
131
+ }>;
132
+ }
133
+
134
+ type ModelResolver = typeof getModel;
135
+ type BedrockStreamer = typeof streamBedrock;
136
+
137
+ function parseDataUrl(url?: string): { mimeType: string; data: string } | null {
138
+ if (!url) return null;
139
+ const match = url.match(/^data:([^;,]+);base64,(.+)$/);
140
+ if (!match) return null;
141
+ return {
142
+ mimeType: match[1]!,
143
+ data: match[2]!,
144
+ };
145
+ }
146
+
147
+ function normalizeMessageContent(
148
+ content: OpenAIMessageContent | null | undefined
149
+ ): Array<OpenAITextPart | OpenAIImagePart> {
150
+ if (typeof content === "string") {
151
+ return content.length > 0 ? [{ type: "text", text: content }] : [];
152
+ }
153
+ return Array.isArray(content) ? content : [];
154
+ }
155
+
156
+ function parseToolArguments(raw?: string): Record<string, unknown> {
157
+ if (!raw?.trim()) return {};
158
+ try {
159
+ const parsed = JSON.parse(raw);
160
+ return parsed && typeof parsed === "object" ? parsed : {};
161
+ } catch {
162
+ return {};
163
+ }
164
+ }
165
+
166
+ export function buildBedrockContext(
167
+ request: OpenAIChatCompletionRequest
168
+ ): BedrockContextPayload {
169
+ const messages: PiMessage[] = [];
170
+ const systemPrompts: string[] = [];
171
+
172
+ for (const message of request.messages || []) {
173
+ if (message.role === "system" || message.role === "developer") {
174
+ const text = normalizeMessageContent(message.content)
175
+ .filter((part): part is OpenAITextPart => part.type === "text")
176
+ .map((part) => part.text)
177
+ .join("\n")
178
+ .trim();
179
+ if (text) systemPrompts.push(text);
180
+ continue;
181
+ }
182
+
183
+ if (message.role === "tool") {
184
+ if (!message.tool_call_id) continue;
185
+ const content = normalizeMessageContent(message.content)
186
+ .map((part) => {
187
+ if (part.type === "text") {
188
+ return { type: "text" as const, text: part.text };
189
+ }
190
+ const image = parseDataUrl(part.image_url?.url);
191
+ return image
192
+ ? {
193
+ type: "image" as const,
194
+ mimeType: image.mimeType,
195
+ data: image.data,
196
+ }
197
+ : null;
198
+ })
199
+ .filter(
200
+ (
201
+ part
202
+ ): part is
203
+ | { type: "text"; text: string }
204
+ | { type: "image"; mimeType: string; data: string } => Boolean(part)
205
+ );
206
+
207
+ messages.push({
208
+ role: "toolResult",
209
+ toolCallId: message.tool_call_id,
210
+ content: content.length > 0 ? content : [{ type: "text", text: "" }],
211
+ });
212
+ continue;
213
+ }
214
+
215
+ if (message.role === "assistant") {
216
+ const content = normalizeMessageContent(message.content)
217
+ .filter((part): part is OpenAITextPart => part.type === "text")
218
+ .map((part) => ({ type: "text" as const, text: part.text }));
219
+
220
+ const toolCalls = (message.tool_calls || []).map((toolCall) => ({
221
+ type: "toolCall" as const,
222
+ id: toolCall.id || crypto.randomUUID(),
223
+ name: toolCall.function?.name || "",
224
+ arguments: parseToolArguments(toolCall.function?.arguments),
225
+ }));
226
+
227
+ const assistantContent = [...content, ...toolCalls];
228
+ if (assistantContent.length > 0) {
229
+ messages.push({
230
+ role: "assistant",
231
+ content: assistantContent,
232
+ });
233
+ }
234
+ continue;
235
+ }
236
+
237
+ const content = normalizeMessageContent(message.content)
238
+ .map((part) => {
239
+ if (part.type === "text") {
240
+ return { type: "text" as const, text: part.text };
241
+ }
242
+ const image = parseDataUrl(part.image_url?.url);
243
+ return image
244
+ ? {
245
+ type: "image" as const,
246
+ mimeType: image.mimeType,
247
+ data: image.data,
248
+ }
249
+ : null;
250
+ })
251
+ .filter(
252
+ (
253
+ part
254
+ ): part is
255
+ | { type: "text"; text: string }
256
+ | { type: "image"; mimeType: string; data: string } => Boolean(part)
257
+ );
258
+
259
+ if (content.length > 0) {
260
+ messages.push({
261
+ role: "user",
262
+ content,
263
+ });
264
+ }
265
+ }
266
+
267
+ const tools = (request.tools || [])
268
+ .filter((tool) => tool.type === "function" && tool.function?.name)
269
+ .map((tool) => ({
270
+ name: tool.function!.name!,
271
+ description: tool.function?.description,
272
+ parameters: tool.function?.parameters || {
273
+ type: "object",
274
+ properties: {},
275
+ additionalProperties: true,
276
+ },
277
+ }));
278
+
279
+ return {
280
+ messages,
281
+ ...(systemPrompts.length > 0
282
+ ? { systemPrompt: systemPrompts.join("\n\n") }
283
+ : {}),
284
+ ...(tools.length > 0 ? { tools } : {}),
285
+ };
286
+ }
287
+
288
+ function mapToolChoice(choice: OpenAIChatCompletionRequest["tool_choice"]):
289
+ | "none"
290
+ | "auto"
291
+ | "any"
292
+ | {
293
+ type: "tool";
294
+ name: string;
295
+ }
296
+ | undefined {
297
+ if (choice === "none" || choice === "auto") return choice;
298
+ if (choice === "required") return "any";
299
+ const toolName = choice?.function?.name?.trim();
300
+ return toolName ? { type: "tool", name: toolName } : undefined;
301
+ }
302
+
303
+ function mapStopReason(reason?: string): "stop" | "length" | "tool_calls" {
304
+ switch (reason) {
305
+ case "length":
306
+ return "length";
307
+ case "toolUse":
308
+ return "tool_calls";
309
+ default:
310
+ return "stop";
311
+ }
312
+ }
313
+
314
+ function createChunk(
315
+ requestModel: string,
316
+ created: number,
317
+ chunkId: string,
318
+ choices: unknown[],
319
+ extra?: Record<string, unknown>
320
+ ) {
321
+ return {
322
+ id: chunkId,
323
+ object: "chat.completion.chunk",
324
+ created,
325
+ model: requestModel,
326
+ choices,
327
+ ...extra,
328
+ };
329
+ }
330
+
331
+ function createSseStream(
332
+ requestModel: string,
333
+ stream: AsyncIterable<BedrockStreamEvent>,
334
+ includeUsage: boolean
335
+ ): ReadableStream<Uint8Array> {
336
+ const encoder = new TextEncoder();
337
+ const created = Math.floor(Date.now() / 1000);
338
+ const chunkId = `chatcmpl-${crypto.randomUUID().replace(/-/g, "")}`;
339
+ const toolIndexes = new Map<number, number>();
340
+ let nextToolIndex = 0;
341
+
342
+ return new ReadableStream<Uint8Array>({
343
+ async start(controller) {
344
+ const writeData = (payload: unknown) => {
345
+ controller.enqueue(
346
+ encoder.encode(`data: ${JSON.stringify(payload)}\n\n`)
347
+ );
348
+ };
349
+
350
+ writeData(
351
+ createChunk(requestModel, created, chunkId, [
352
+ {
353
+ index: 0,
354
+ delta: { role: "assistant" },
355
+ finish_reason: null,
356
+ },
357
+ ])
358
+ );
359
+
360
+ try {
361
+ for await (const event of stream) {
362
+ if (event.type === "text_delta" && event.delta) {
363
+ writeData(
364
+ createChunk(requestModel, created, chunkId, [
365
+ {
366
+ index: 0,
367
+ delta: { content: event.delta },
368
+ finish_reason: null,
369
+ },
370
+ ])
371
+ );
372
+ continue;
373
+ }
374
+
375
+ if (event.type === "thinking_delta" && event.delta) {
376
+ writeData(
377
+ createChunk(requestModel, created, chunkId, [
378
+ {
379
+ index: 0,
380
+ delta: { reasoning_content: event.delta },
381
+ finish_reason: null,
382
+ },
383
+ ])
384
+ );
385
+ continue;
386
+ }
387
+
388
+ if (
389
+ event.type === "toolcall_start" &&
390
+ event.contentIndex !== undefined
391
+ ) {
392
+ const toolIndex = nextToolIndex++;
393
+ toolIndexes.set(event.contentIndex, toolIndex);
394
+ writeData(
395
+ createChunk(requestModel, created, chunkId, [
396
+ {
397
+ index: 0,
398
+ delta: {
399
+ tool_calls: [
400
+ {
401
+ index: toolIndex,
402
+ id: event.toolCall?.id || "",
403
+ type: "function",
404
+ function: {
405
+ name: event.toolCall?.name || "",
406
+ arguments: "",
407
+ },
408
+ },
409
+ ],
410
+ },
411
+ finish_reason: null,
412
+ },
413
+ ])
414
+ );
415
+ continue;
416
+ }
417
+
418
+ if (
419
+ event.type === "toolcall_delta" &&
420
+ event.contentIndex !== undefined
421
+ ) {
422
+ const toolIndex = toolIndexes.get(event.contentIndex) ?? 0;
423
+ writeData(
424
+ createChunk(requestModel, created, chunkId, [
425
+ {
426
+ index: 0,
427
+ delta: {
428
+ tool_calls: [
429
+ {
430
+ index: toolIndex,
431
+ function: {
432
+ arguments: event.delta || "",
433
+ },
434
+ },
435
+ ],
436
+ },
437
+ finish_reason: null,
438
+ },
439
+ ])
440
+ );
441
+ continue;
442
+ }
443
+
444
+ if (event.type === "done") {
445
+ writeData(
446
+ createChunk(requestModel, created, chunkId, [
447
+ {
448
+ index: 0,
449
+ delta: {},
450
+ finish_reason: mapStopReason(event.reason),
451
+ },
452
+ ])
453
+ );
454
+
455
+ if (includeUsage) {
456
+ const usage = event.message?.usage || {};
457
+ const promptTokens = (usage.input || 0) + (usage.cacheRead || 0);
458
+ const completionTokens = usage.output || 0;
459
+ writeData(
460
+ createChunk(requestModel, created, chunkId, [], {
461
+ usage: {
462
+ prompt_tokens: promptTokens,
463
+ completion_tokens: completionTokens,
464
+ total_tokens:
465
+ usage.totalTokens || promptTokens + completionTokens,
466
+ prompt_tokens_details: {
467
+ cached_tokens: usage.cacheRead || 0,
468
+ },
469
+ },
470
+ })
471
+ );
472
+ }
473
+
474
+ controller.enqueue(encoder.encode("data: [DONE]\n\n"));
475
+ controller.close();
476
+ return;
477
+ }
478
+
479
+ if (event.type === "error") {
480
+ controller.error(
481
+ new Error(event.error?.errorMessage || "Bedrock request failed")
482
+ );
483
+ return;
484
+ }
485
+ }
486
+
487
+ writeData(
488
+ createChunk(requestModel, created, chunkId, [
489
+ {
490
+ index: 0,
491
+ delta: {},
492
+ finish_reason: "stop",
493
+ },
494
+ ])
495
+ );
496
+ controller.enqueue(encoder.encode("data: [DONE]\n\n"));
497
+ controller.close();
498
+ } catch (error) {
499
+ controller.error(error);
500
+ }
501
+ },
502
+ });
503
+ }
504
+
505
+ export interface BedrockOpenAIServiceOptions {
506
+ modelCatalog?: BedrockModelCatalog;
507
+ modelResolver?: ModelResolver;
508
+ bedrockStreamer?: BedrockStreamer;
509
+ }
510
+
511
+ export class BedrockOpenAIService {
512
+ private readonly app = new Hono();
513
+ private readonly modelCatalog: BedrockModelCatalog;
514
+ private readonly modelResolver: ModelResolver;
515
+ private readonly bedrockStreamer: BedrockStreamer;
516
+
517
+ constructor(options: BedrockOpenAIServiceOptions = {}) {
518
+ this.modelCatalog = options.modelCatalog || new BedrockModelCatalog();
519
+ this.modelResolver = options.modelResolver || getModel;
520
+ this.bedrockStreamer = options.bedrockStreamer || streamBedrock;
521
+ this.setupRoutes();
522
+ }
523
+
524
+ getApp(): Hono {
525
+ return this.app;
526
+ }
527
+
528
+ private setupRoutes(): void {
529
+ this.app.get("/health", (c) =>
530
+ c.json({
531
+ service: "bedrock-openai-service",
532
+ status: "enabled",
533
+ region: resolveAwsRegion() || null,
534
+ })
535
+ );
536
+
537
+ this.app.get("/openai/a/:agentId/v1/models", async (c) => {
538
+ const models = await this.modelCatalog.listModelOptions();
539
+ return c.json({
540
+ object: "list",
541
+ data: models.map((model) => ({
542
+ id: model.id,
543
+ object: "model",
544
+ created: 0,
545
+ owned_by: "amazon-bedrock",
546
+ })),
547
+ });
548
+ });
549
+
550
+ this.app.post("/openai/a/:agentId/v1/chat/completions", async (c) =>
551
+ this.handleChatCompletions(c)
552
+ );
553
+ }
554
+
555
+ private async resolveRuntimeModel(
556
+ requestedModel: string
557
+ ): Promise<Model<"bedrock-converse-stream">> {
558
+ const staticModel = this.modelResolver(
559
+ "amazon-bedrock" as never,
560
+ requestedModel as never
561
+ ) as Model<"bedrock-converse-stream"> | undefined;
562
+
563
+ if (staticModel) return staticModel;
564
+
565
+ const discoveredModel = await this.modelCatalog.getModel(requestedModel);
566
+ return buildDynamicBedrockModel(requestedModel, discoveredModel);
567
+ }
568
+
569
+ private async handleChatCompletions(c: Context): Promise<Response> {
570
+ const request = (await c.req
571
+ .json()
572
+ .catch(() => null)) as OpenAIChatCompletionRequest | null;
573
+
574
+ if (!request?.model) {
575
+ return c.json(
576
+ {
577
+ error: {
578
+ type: "invalid_request_error",
579
+ message: "Missing required field: model",
580
+ },
581
+ },
582
+ 400
583
+ );
584
+ }
585
+
586
+ if (request.stream === false) {
587
+ return c.json(
588
+ {
589
+ error: {
590
+ type: "invalid_request_error",
591
+ message: "Only stream=true is supported for Amazon Bedrock",
592
+ },
593
+ },
594
+ 400
595
+ );
596
+ }
597
+
598
+ const region = resolveAwsRegion();
599
+ if (!region) {
600
+ return c.json(
601
+ {
602
+ error: {
603
+ type: "invalid_request_error",
604
+ message: "AWS region is not configured on the gateway",
605
+ },
606
+ },
607
+ 503
608
+ );
609
+ }
610
+
611
+ const runtimeModel = await this.resolveRuntimeModel(request.model);
612
+ const context = buildBedrockContext(request);
613
+ const stopSequences = Array.isArray(request.stop)
614
+ ? request.stop
615
+ : typeof request.stop === "string"
616
+ ? [request.stop]
617
+ : [];
618
+ const toolChoice = mapToolChoice(request.tool_choice);
619
+
620
+ logger.info(
621
+ {
622
+ modelId: request.model,
623
+ region,
624
+ toolCount: context.tools?.length || 0,
625
+ },
626
+ "Proxying OpenAI-style Bedrock request"
627
+ );
628
+
629
+ const stream = this.bedrockStreamer(
630
+ runtimeModel as never,
631
+ context as never,
632
+ {
633
+ signal: c.req.raw.signal,
634
+ region,
635
+ maxTokens: request.max_completion_tokens ?? request.max_tokens,
636
+ temperature: request.temperature,
637
+ ...(stopSequences.length > 0 ? { stopSequences } : {}),
638
+ ...(toolChoice ? { toolChoice } : {}),
639
+ }
640
+ ) as AsyncIterable<BedrockStreamEvent>;
641
+
642
+ return new Response(
643
+ createSseStream(
644
+ request.model,
645
+ stream,
646
+ request.stream_options?.include_usage === true
647
+ ),
648
+ {
649
+ headers: {
650
+ "Content-Type": "text/event-stream; charset=utf-8",
651
+ "Cache-Control": "no-cache, no-transform",
652
+ Connection: "keep-alive",
653
+ "X-Accel-Buffering": "no",
654
+ },
655
+ }
656
+ );
657
+ }
658
+ }
@@ -11,6 +11,7 @@ import {
11
11
  } from "@lobu/core";
12
12
  import { AgentMetadataStore } from "../auth/agent-metadata-store";
13
13
  import { ApiKeyProviderModule } from "../auth/api-key-provider-module";
14
+ import { BedrockProviderModule } from "../auth/bedrock/provider-module";
14
15
  import { ChatGPTOAuthModule } from "../auth/chatgpt";
15
16
  import { ClaudeOAuthModule } from "../auth/claude/oauth-module";
16
17
  import { ExternalAuthClient } from "../auth/external/client";
@@ -57,6 +58,8 @@ import {
57
58
  RedisAgentConnectionStore,
58
59
  } from "../stores/redis-agent-store";
59
60
  import { ImageGenerationService } from "./image-generation-service";
61
+ import { BedrockModelCatalog } from "./bedrock-model-catalog";
62
+ import { BedrockOpenAIService } from "./bedrock-openai-service";
60
63
  import { InstructionService } from "./instruction-service";
61
64
  import { RedisSessionStore, SessionManager } from "./session-manager";
62
65
  import { SettingsResolver } from "./settings-resolver";
@@ -121,6 +124,7 @@ export class CoreServices {
121
124
  private channelBindingService?: ChannelBindingService;
122
125
  private transcriptionService?: TranscriptionService;
123
126
  private imageGenerationService?: ImageGenerationService;
127
+ private bedrockOpenAIService?: BedrockOpenAIService;
124
128
  private userAgentsStore?: UserAgentsStore;
125
129
  private agentMetadataStore?: AgentMetadataStore;
126
130
 
@@ -550,6 +554,17 @@ export class CoreServices {
550
554
  `ChatGPT OAuth module registered (system token: ${chatgptOAuthModule.hasSystemKey() ? "available" : "not available"})`
551
555
  );
552
556
 
557
+ const bedrockModelCatalog = new BedrockModelCatalog();
558
+ const bedrockProviderModule = new BedrockProviderModule(
559
+ this.authProfilesManager,
560
+ bedrockModelCatalog
561
+ );
562
+ moduleRegistry.register(bedrockProviderModule);
563
+ this.bedrockOpenAIService = new BedrockOpenAIService({
564
+ modelCatalog: bedrockModelCatalog,
565
+ });
566
+ logger.debug("Bedrock provider module registered");
567
+
553
568
  // Initialize system skills — use injected skills if provided, else load from file
554
569
  if (this.options?.systemSkills) {
555
570
  this.systemSkillsService = new SystemSkillsService(
@@ -1007,6 +1022,10 @@ export class CoreServices {
1007
1022
  return this.imageGenerationService;
1008
1023
  }
1009
1024
 
1025
+ getBedrockOpenAIService(): BedrockOpenAIService | undefined {
1026
+ return this.bedrockOpenAIService;
1027
+ }
1028
+
1010
1029
  getUserAgentsStore(): UserAgentsStore {
1011
1030
  if (!this.userAgentsStore)
1012
1031
  throw new Error("User agents store not initialized");
@@ -43,7 +43,6 @@ export class SystemConfigResolver {
43
43
  config.args = [...mcp.args];
44
44
  }
45
45
  if (mcp.oauth) config.oauth = mcp.oauth;
46
- if (mcp.resource) config.resource = mcp.resource;
47
46
  if (mcp.inputs) config.inputs = mcp.inputs;
48
47
  if (mcp.headers) config.headers = mcp.headers;
49
48