@hilbras/omninode 2.0.0-alpha.6 → 2.0.0-alpha.7

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/README.md CHANGED
@@ -18,9 +18,9 @@ existing intelligence; it does not try to become another model.
18
18
 
19
19
  ## Status
20
20
 
21
- **v2.0.0-alpha.6 — v2 Phase 6: Provider Infrastructure v2**
21
+ **v2.0.0-alpha.7 — v2 Phase 7: OmniHilbras Integration v2**
22
22
  (the reliability & interoperability line: architecture → execution →
23
- protocol → adapters → pipelines → providers) (the v2
23
+ protocol → adapters → pipelines → providers → OmniHilbras) (the v2
24
24
  roadmap is [docs/ROADMAP_V2.md](docs/ROADMAP_V2.md); the v1 plan is complete:
25
25
  the
26
26
  full loop from §28 runs in one command —
@@ -31,6 +31,12 @@ for the plan and [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) for the design).
31
31
 
32
32
  ### What's new in v2 (so far)
33
33
 
34
+ - **Phase 7 — OmniHilbras Integration v2**: the dedicated adapter now
35
+ captures gateway-level metadata (version, tier, region) and attaches it
36
+ to discovered models, honors provider-level `timeout_ms`, and supports
37
+ streaming completions (`stream()` over SSE) — while a guard test proves
38
+ the core references OmniHilbras *only* through the provider layer, and a
39
+ full pipeline runs with no OmniHilbras configured at all.
34
40
  - **Phase 6 — Provider Infrastructure v2**: standardized provider contract
35
41
  (`providerId`, secret-free `authentication`, declared `capabilities`,
36
42
  `connect()` / `getModel()` discovery), richer model metadata (context
package/dist/cli/index.js CHANGED
@@ -6,7 +6,7 @@ import { realpathSync } from "fs";
6
6
  import { Command } from "commander";
7
7
 
8
8
  // src/version.ts
9
- var OMNINODE_VERSION = "2.0.0-alpha.6";
9
+ var OMNINODE_VERSION = "2.0.0-alpha.7";
10
10
 
11
11
  // src/errors/index.ts
12
12
  var OmniNodeError = class extends Error {
@@ -68,6 +68,7 @@ var providerConfigSchema = z.object({
68
68
  type: z.enum(["openai-compatible", "omnihilbras", "openrouter", "local", "custom"]),
69
69
  base_url: z.string().url(),
70
70
  api_key_env_var: z.string().min(1).optional(),
71
+ timeout_ms: z.number().int().positive().optional(),
71
72
  headers: z.record(z.string()).optional(),
72
73
  enabled: z.boolean().optional(),
73
74
  metadata: z.record(z.unknown()).optional()
@@ -132,7 +133,8 @@ var projectConfigSchema = z.object({
132
133
  memory: z.object({
133
134
  provider: z.string().min(1).default("local"),
134
135
  base_url: z.string().url().optional(),
135
- api_key_env_var: z.string().min(1).optional()
136
+ api_key_env_var: z.string().min(1).optional(),
137
+ timeout_ms: z.number().int().positive().optional()
136
138
  }).strict().optional()
137
139
  }).strict();
138
140
  var appConfigSchema = z.object({
@@ -245,6 +247,7 @@ function toProviderConfig(raw) {
245
247
  type: raw.type,
246
248
  baseUrl: raw.base_url,
247
249
  apiKeyEnvVar: raw.api_key_env_var,
250
+ timeoutMs: raw.timeout_ms,
248
251
  headers: raw.headers,
249
252
  enabled: raw.enabled,
250
253
  metadata: raw.metadata
@@ -546,6 +549,7 @@ function providerTransportError(context) {
546
549
  }
547
550
 
548
551
  // src/providers/openai-compatible/index.ts
552
+ var DEFAULT_REQUEST_TIMEOUT_MS = 3e4;
549
553
  var OpenAICompatibleProvider = class {
550
554
  config;
551
555
  providerId;
@@ -565,6 +569,10 @@ var OpenAICompatibleProvider = class {
565
569
  };
566
570
  this.log = log.child({ provider: config.name });
567
571
  }
572
+ /** Provider-level request timeout (roadmap §6.6/§11) with the shared default. */
573
+ get requestTimeoutMs() {
574
+ return this.config.timeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;
575
+ }
568
576
  /** Authenticate + discover (roadmap §10, Model Discovery). */
569
577
  async connect() {
570
578
  authHeaders(this.config, this.env);
@@ -576,11 +584,17 @@ var OpenAICompatibleProvider = class {
576
584
  const models = this.discovered ?? await this.connect();
577
585
  return models.find((m) => m.id === modelId);
578
586
  }
579
- async listModels() {
587
+ /** Raw /models body (protected so adapters can capture gateway-level metadata). */
588
+ async rawModelsResponse() {
589
+ const response = await this.modelsRequest();
590
+ return response.body;
591
+ }
592
+ async modelsRequest() {
580
593
  let response;
581
594
  try {
582
595
  response = await requestJson(`${this.config.baseUrl}/models`, {
583
- headers: authHeaders(this.config, this.env)
596
+ headers: authHeaders(this.config, this.env),
597
+ timeoutMs: this.requestTimeoutMs
584
598
  });
585
599
  } catch (error) {
586
600
  if (error instanceof ProviderError) throw error;
@@ -595,6 +609,10 @@ var OpenAICompatibleProvider = class {
595
609
  body
596
610
  });
597
611
  }
612
+ return response;
613
+ }
614
+ async listModels() {
615
+ const body = await this.rawModelsResponse();
598
616
  const entries = extractModelEntries(body, this.config);
599
617
  const models = [];
600
618
  let skipped = 0;
@@ -637,6 +655,10 @@ var OpenAICompatibleProvider = class {
637
655
  }
638
656
  };
639
657
  }
658
+ /** Streaming where the gateway supports it (§11). */
659
+ stream(request, onChunk) {
660
+ return streamChatCompletion(this.config, request, onChunk, this.env);
661
+ }
640
662
  async healthCheck() {
641
663
  const lastCheckedAt = (/* @__PURE__ */ new Date()).toISOString();
642
664
  try {
@@ -671,6 +693,7 @@ var OpenAICompatibleProvider = class {
671
693
  response = await requestJson(`${this.config.baseUrl}/chat/completions`, {
672
694
  method: "POST",
673
695
  headers: authHeaders(this.config, this.env),
696
+ timeoutMs: this.requestTimeoutMs,
674
697
  body: {
675
698
  model: request.model,
676
699
  messages: request.messages,
@@ -715,6 +738,74 @@ var OpenAICompatibleProvider = class {
715
738
  };
716
739
  }
717
740
  };
741
+ async function streamChatCompletion(config, request, onChunk, env = process.env) {
742
+ const baseUrl = normalizeBaseUrl(config.baseUrl);
743
+ const timeoutMs = config.timeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;
744
+ let response;
745
+ try {
746
+ response = await fetch(`${baseUrl}/chat/completions`, {
747
+ method: "POST",
748
+ headers: {
749
+ accept: "text/event-stream",
750
+ "content-type": "application/json",
751
+ ...authHeaders(config, env)
752
+ },
753
+ body: JSON.stringify({
754
+ model: request.model,
755
+ messages: request.messages,
756
+ stream: true,
757
+ ...request.temperature !== void 0 ? { temperature: request.temperature } : {},
758
+ ...request.maxTokens !== void 0 ? { max_tokens: request.maxTokens } : {}
759
+ }),
760
+ signal: AbortSignal.timeout(timeoutMs)
761
+ });
762
+ } catch (error) {
763
+ throw providerTransportError({ provider: config.name, operation: "stream", cause: error });
764
+ }
765
+ if (!response.ok) {
766
+ const body = await response.json().catch(() => void 0);
767
+ throw providerHttpError({ provider: config.name, operation: "stream", status: response.status, body });
768
+ }
769
+ if (!response.body) {
770
+ throw providerHttpError({
771
+ provider: config.name,
772
+ operation: "stream",
773
+ status: 500,
774
+ body: { error: { message: "streaming is not supported by this endpoint" } }
775
+ });
776
+ }
777
+ let content = "";
778
+ let model = request.model;
779
+ let finishReason;
780
+ const decoder = new TextDecoder();
781
+ let buffer = "";
782
+ for await (const piece of response.body) {
783
+ buffer += decoder.decode(piece, { stream: true });
784
+ let newline = buffer.indexOf("\n");
785
+ while (newline >= 0) {
786
+ const line = buffer.slice(0, newline).trim();
787
+ buffer = buffer.slice(newline + 1);
788
+ newline = buffer.indexOf("\n");
789
+ if (line.length === 0 || line.startsWith(":")) continue;
790
+ const data = line.startsWith("data:") ? line.slice(5).trim() : line;
791
+ if (data === "[DONE]") continue;
792
+ let event;
793
+ try {
794
+ event = JSON.parse(data);
795
+ } catch {
796
+ continue;
797
+ }
798
+ if (typeof event.model === "string") model = event.model;
799
+ const choices = Array.isArray(event.choices) ? event.choices : [];
800
+ const first = choices[0];
801
+ const delta = typeof first?.delta?.content === "string" ? first.delta.content : "";
802
+ if (delta.length > 0) content += delta;
803
+ if (typeof first?.finish_reason === "string") finishReason = first.finish_reason;
804
+ onChunk({ delta, ...finishReason !== void 0 ? { finishReason } : {}, raw: event });
805
+ }
806
+ }
807
+ return { model, content, ...finishReason !== void 0 ? { finishReason } : {} };
808
+ }
718
809
  function normalizeModalities(value) {
719
810
  const list = Array.isArray(value) ? value : typeof value === "string" ? [value] : void 0;
720
811
  if (!list) return void 0;
@@ -748,10 +839,32 @@ function inferHealth(error) {
748
839
 
749
840
  // src/providers/omnihilbras/index.ts
750
841
  var OmniHilbrasProvider = class extends OpenAICompatibleProvider {
842
+ gateway;
843
+ async listModels() {
844
+ const raw = await this.rawModelsResponse();
845
+ await this.extractGatewayMetadata(raw);
846
+ const models = await super.listModels();
847
+ return this.gateway ? models.map((model) => ({ ...model, metadata: { ...model.metadata, gateway: this.gateway } })) : models;
848
+ }
751
849
  /**
752
850
  * Run the full connect flow. Throws on authentication or transport failure
753
851
  * (use healthCheck() for a non-throwing probe).
754
852
  */
853
+ /**
854
+ * Gateway metadata reported alongside the model list (gateway version, tier,
855
+ * region…), kept separate from per-model metadata (§11 — provider metadata).
856
+ */
857
+ gatewayMetadata() {
858
+ return this.gateway;
859
+ }
860
+ async extractGatewayMetadata(body) {
861
+ if (body !== null && typeof body === "object") {
862
+ const gateway = body.gateway;
863
+ if (gateway !== null && typeof gateway === "object") {
864
+ this.gateway = gateway;
865
+ }
866
+ }
867
+ }
755
868
  async connectAndRegister(registry) {
756
869
  const apiKey = resolveApiKey(this.config);
757
870
  const discovered = await this.listModels();