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

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/dist/index.d.ts CHANGED
@@ -334,14 +334,25 @@ interface ModelCapabilities {
334
334
  embeddings?: boolean;
335
335
  }
336
336
  type ModelStatus = "available" | "unavailable" | "deprecated";
337
+ type Modality = "text" | "image" | "audio" | "video" | "embedding";
338
+ /** Model metadata (roadmap §10, Model Metadata). */
337
339
  interface ModelInfo {
338
340
  /** Name of the provider this model belongs to. */
339
341
  provider: string;
340
342
  /** Provider-specific model identifier, e.g. "qwen3-coder". */
341
343
  id: string;
342
344
  displayName?: string;
345
+ /** Human-readable model name reported by the provider, when available. */
346
+ name?: string;
343
347
  capabilities?: ModelCapabilities;
344
348
  contextWindow?: number;
349
+ inputTypes?: Modality[];
350
+ outputTypes?: Modality[];
351
+ supportsTools?: boolean;
352
+ supportsVision?: boolean;
353
+ supportsReasoning?: boolean;
354
+ supportsStructuredOutput?: boolean;
355
+ supportsStreaming?: boolean;
345
356
  status?: ModelStatus;
346
357
  metadata?: Record<string, unknown>;
347
358
  }
@@ -650,7 +661,20 @@ interface Project {
650
661
 
651
662
  type ProviderType = "openai-compatible" | "omnihilbras" | "openrouter" | "local" | "custom";
652
663
  type ProviderHealth = "unknown" | "healthy" | "degraded" | "unreachable";
664
+ /** Declared provider capabilities (roadmap §10, Provider Interface). */
665
+ type ProviderCapability = "chat" | "models" | "streaming" | "tools" | "embeddings" | "vision";
666
+ /** Normalized authentication description — never contains a secret value. */
667
+ interface ProviderAuthentication {
668
+ /** How the provider authenticates. */
669
+ method: "bearer" | "none" | "custom";
670
+ /** Environment variable that holds the credential, if any. */
671
+ envVar?: string;
672
+ /** True when the referenced variable is currently set. */
673
+ configured: boolean;
674
+ }
653
675
  interface ProviderConfig {
676
+ /** Stable identifier used in records and references (defaults to `name`). */
677
+ providerId?: string;
654
678
  /** Unique name used in project configuration and the model registry. */
655
679
  name: string;
656
680
  type: ProviderType;
@@ -661,6 +685,8 @@ interface ProviderConfig {
661
685
  */
662
686
  apiKeyEnvVar?: string;
663
687
  headers?: Record<string, string>;
688
+ /** Capabilities declared for this provider (discovered ones are merged in). */
689
+ capabilities?: ProviderCapability[];
664
690
  enabled?: boolean;
665
691
  metadata?: Record<string, unknown>;
666
692
  }
@@ -674,11 +700,22 @@ interface ProviderStatus {
674
700
  lastCheckedAt?: string;
675
701
  message?: string;
676
702
  }
677
- /** Contract every provider adapter implements (Phase 1+). */
703
+ /** Contract every provider adapter implements (roadmap §10). */
678
704
  interface IProvider {
679
705
  readonly config: ProviderConfig;
706
+ /** Stable identifier of this provider. */
707
+ readonly providerId: string;
708
+ /** Normalized authentication description (no secret values). */
709
+ readonly authentication: ProviderAuthentication;
710
+ /**
711
+ * Connect: authenticate and discover models (roadmap §10, Model Discovery).
712
+ * Returns the discovered models.
713
+ */
714
+ connect(): Promise<ModelInfo[]>;
680
715
  /** Discover models exposed by this provider. */
681
716
  listModels(): Promise<ModelInfo[]>;
717
+ /** Fetch a single model's metadata (from discovery; re-discovers if unknown). */
718
+ getModel(modelId: string): Promise<ModelInfo | undefined>;
682
719
  /** Check connectivity and authentication. */
683
720
  healthCheck(): Promise<ProviderStatus>;
684
721
  }
@@ -1842,11 +1879,56 @@ declare function requestJson(url: string, options?: HttpRequestOptions): Promise
1842
1879
  declare const OPENAI_COMPATIBLE_TYPES: readonly ProviderType[];
1843
1880
  declare function createProvider(config: ProviderConfig): IChatProvider;
1844
1881
 
1882
+ /**
1883
+ * Provider error normalization (roadmap §10).
1884
+ *
1885
+ * Every provider failure is mapped to one stable kind, so callers can react
1886
+ * (retry a rate limit, surface an auth problem, treat a missing model
1887
+ * differently) without string-matching provider-specific payloads.
1888
+ */
1889
+
1890
+ type ProviderErrorKind = "AUTHENTICATION_ERROR" | "RATE_LIMIT_ERROR" | "INVALID_REQUEST" | "MODEL_NOT_FOUND" | "TIMEOUT" | "NETWORK_ERROR" | "SERVER_ERROR" | "UNKNOWN_ERROR";
1891
+ interface ProviderErrorContext {
1892
+ provider: string;
1893
+ operation: string;
1894
+ status?: number;
1895
+ body?: unknown;
1896
+ retryAfterMs?: number;
1897
+ cause?: unknown;
1898
+ /** Maps the normalized kind onto the legacy stable error code. */
1899
+ code?: "PROVIDER_UNAVAILABLE" | "PROVIDER_AUTH_FAILED" | "MODEL_NOT_FOUND";
1900
+ }
1901
+ /** Kinds worth retrying automatically (rate limits and transient server errors). */
1902
+ declare function isRetryable(kind: ProviderErrorKind): boolean;
1903
+ /** Maps an HTTP status (and optional body) to a normalized kind. */
1904
+ declare function kindFromStatus(status: number): ProviderErrorKind;
1905
+ /** Extracts a human message from common provider error body shapes. */
1906
+ declare function extractMessage(body: unknown): string | undefined;
1907
+ /** Parses `Retry-After` (seconds or HTTP-date) into milliseconds. */
1908
+ declare function parseRetryAfter(value: string | null | undefined, now?: number): number | undefined;
1909
+ /** True for the abort/timeout errors raised by fetch's AbortSignal.timeout. */
1910
+ declare function isTimeoutError(error: unknown): boolean;
1911
+ /** True for transport failures (DNS, refused, TLS, offline). */
1912
+ declare function isNetworkError(error: unknown): boolean;
1913
+ declare function providerHttpError(context: ProviderErrorContext): ProviderError;
1914
+ /** Wraps a thrown transport/abort error into a normalized ProviderError. */
1915
+ declare function providerTransportError(context: Omit<ProviderErrorContext, "status" | "body">): ProviderError;
1916
+
1845
1917
  declare class OpenAICompatibleProvider implements IChatProvider {
1846
1918
  readonly config: ProviderConfig;
1919
+ readonly providerId: string;
1920
+ readonly authentication: ProviderAuthentication;
1847
1921
  private readonly log;
1848
- constructor(config: ProviderConfig, log?: Logger);
1922
+ /** Discovered models, so getModel() answers without another round-trip. */
1923
+ private discovered?;
1924
+ private readonly env;
1925
+ constructor(config: ProviderConfig, log?: Logger, env?: Record<string, string | undefined>);
1926
+ /** Authenticate + discover (roadmap §10, Model Discovery). */
1927
+ connect(): Promise<ModelInfo[]>;
1928
+ getModel(modelId: string): Promise<ModelInfo | undefined>;
1849
1929
  listModels(): Promise<ModelInfo[]>;
1930
+ /** Maps a raw model entry onto the normalized metadata shape (§10). */
1931
+ private toModelInfo;
1850
1932
  healthCheck(): Promise<ProviderStatus>;
1851
1933
  chat(request: ChatRequest): Promise<ChatResponse>;
1852
1934
  }
@@ -1893,7 +1975,7 @@ declare class OmniHilbrasProvider extends OpenAICompatibleProvider {
1893
1975
  * Run the full connect flow. Throws on authentication or transport failure
1894
1976
  * (use healthCheck() for a non-throwing probe).
1895
1977
  */
1896
- connect(registry?: ModelRegistry): Promise<OmniHilbrasConnectionResult>;
1978
+ connectAndRegister(registry?: ModelRegistry): Promise<OmniHilbrasConnectionResult>;
1897
1979
  }
1898
1980
 
1899
1981
  /**
@@ -2563,6 +2645,6 @@ declare class ProjectStores {
2563
2645
  constructor(directory?: string);
2564
2646
  }
2565
2647
 
2566
- declare const OMNINODE_VERSION = "2.0.0-alpha.5";
2648
+ declare const OMNINODE_VERSION = "2.0.0-alpha.6";
2567
2649
 
2568
- export { AgentAdapterRegistry, type AgentConfig, type AgentConfigYaml, type AgentDescriptor, AgentError, type AgentInfo, type AgentInputMode, type AgentIntegrationType, AgentRegistry, type AgentStatus, type AgentTaskInput, type AgentTaskOutput, type AgentToNodeMessage, type AggregateOptions, type AggregatedFinding, type AggregatedRecommendation, type AppConfig, type AppConfigYaml, type ArtifactKind, type ArtifactPayload, type AskedQuestion, type AuditAction, type AuditEvent, type AuditSink, type ChatFn, type ChatMessage, type ChatRequest, type ChatResponse, type ChatRole, type ChatUsage, type CombinedMeta, type CombinedReport, type CombinedReportFilter, type Confidence, ConfigError, ConsoleLogSink, type CreateTaskInput, type CwdValidation, DEFAULT_MAX_OUTPUT_BYTES, type DecodeResult, type DiscoveryResult, type EnvPolicy, type EnvelopeMeta, type ExecutionRecord, type ExtractionMeta, FileAuditLog, FilePipelineRunStore, FilePlanStore, FileReportStore, FileTaskStore, type Finding, type FindingSeverity, type FindingSource, type HelloResult, HeuristicPlanner, type HttpRequestOptions, type HttpResponse, type IAgent, type IAgentAdapter, type IChatProvider, type IMemoryProvider, type IPipelineExecutor, type IPlanner, type IProvider, type JsonDocument, JsonFileStore, type JsonFileStoreOptions, type LoadConfigOptions, LocalMemoryProvider, type LogLevel, type LogSink, Logger, type LoggerOptions, MAX_MESSAGE_BYTES, type MemoryContextQuery, type MemoryEntry, MemoryError, type MemoryProviderConfig, type MemoryQuery, type MemoryScope, MemoryService, type MessageEnvelope, type ModelCapabilities, type ModelFilter, type ModelInfo, ModelPlanner, ModelRegistry, type ModelStatus, type NodeToAgentMessage, OMNINODE_VERSION, OPENAI_COMPATIBLE_TYPES, type OmniHilbrasConnectionResult, OmniHilbrasProvider, OmniNodeError, type OmniNodeErrorCode, type OmniNodeErrorOptions, OpenAICompatibleProvider, PROTOCOL_V2, type PipelineConfigYaml, type PipelineDefinition, PipelineEngine, type PipelineEngineOptions, PipelineError, type PipelineRun, type PipelineRunAttempt, type PipelineRunFilter, type PipelineRunOptions, type PipelineRunStatus, type PipelineRunStore, type PipelineStep, type PipelineStepCondition, type PipelineStepConfigYaml, type PipelineStepKind, type PipelineStepRun, type Plan, type PlanFilter, type PlanRequest, type PlanStep, type PlanStore, type PlannerConfigYaml, PlannerError, ProcessAgent, ProcessAgentAdapter, type Project, type ProjectConfigYaml, ProjectStores, type ProtocolAgentToNodeType, ProtocolDecoder, type ProtocolEnvelope, ProtocolError, type ProtocolMessageType, type ProtocolNodeToAgentType, ProtocolSession, type ProtocolViolation, type ProtocolViolationCode, type ProviderConfig, type ProviderConfigYaml, ProviderError, type ProviderHealth, type ProviderStatus, type ProviderType, type QuestionPayload, type QuestionResponder, type RememberaConfig, RememberaMemoryProvider, type Report, type ReportFilter, type ReportPayload, ReportService, type ReportStore, type RoleConfigYaml, type RoleDefinition, RoleRegistry, type RunPipelineOptions, SUPPORTED_PROTOCOL_VERSIONS, type SessionIds, TERMINAL_TASK_STATUSES, type Task, type TaskContext, TaskEngine, type TaskEngineOptions, TaskError, type TaskPayload, type TaskResult, type TaskStatus, type TaskStore, type TaskStoreFilter, type WritableLike, agentConfigSchema, aggregateReports, appConfigSchema, authHeaders, buildAgentRegistry, buildChildEnv, buildHello, buildHelloAck, buildPipelineEngine, buildPlanner, buildPlannerContext, buildRoleRegistry, composeTaskText, createAgent, createDefaultChatFn, createMemoryProvider, createProvider, createTaskEngine, decodeMessage, decodeStream, defaultProjectConfigYaml, describeExit, encodeMessage, envelope, expandEnvRefs, extractReportFromText, findConfigFile, isOmniNodeError, isSupportedProtocol, keywords, killTree, loadConfig, logger, newMessageId, parseHello, parsePlanJson, pipelineConfigSchema, pipelineStepConfigSchema, plannerConfigSchema, projectConfigSchema, providerConfigSchema, registryKey, requestJson, requireCompletion, resolveApiKey, resolveEnvPolicy, roleConfigSchema, sanitizeChunk, similar, validateEnvelope, validateWorkingDirectory };
2650
+ export { AgentAdapterRegistry, type AgentConfig, type AgentConfigYaml, type AgentDescriptor, AgentError, type AgentInfo, type AgentInputMode, type AgentIntegrationType, AgentRegistry, type AgentStatus, type AgentTaskInput, type AgentTaskOutput, type AgentToNodeMessage, type AggregateOptions, type AggregatedFinding, type AggregatedRecommendation, type AppConfig, type AppConfigYaml, type ArtifactKind, type ArtifactPayload, type AskedQuestion, type AuditAction, type AuditEvent, type AuditSink, type ChatFn, type ChatMessage, type ChatRequest, type ChatResponse, type ChatRole, type ChatUsage, type CombinedMeta, type CombinedReport, type CombinedReportFilter, type Confidence, ConfigError, ConsoleLogSink, type CreateTaskInput, type CwdValidation, DEFAULT_MAX_OUTPUT_BYTES, type DecodeResult, type DiscoveryResult, type EnvPolicy, type EnvelopeMeta, type ExecutionRecord, type ExtractionMeta, FileAuditLog, FilePipelineRunStore, FilePlanStore, FileReportStore, FileTaskStore, type Finding, type FindingSeverity, type FindingSource, type HelloResult, HeuristicPlanner, type HttpRequestOptions, type HttpResponse, type IAgent, type IAgentAdapter, type IChatProvider, type IMemoryProvider, type IPipelineExecutor, type IPlanner, type IProvider, type JsonDocument, JsonFileStore, type JsonFileStoreOptions, type LoadConfigOptions, LocalMemoryProvider, type LogLevel, type LogSink, Logger, type LoggerOptions, MAX_MESSAGE_BYTES, type MemoryContextQuery, type MemoryEntry, MemoryError, type MemoryProviderConfig, type MemoryQuery, type MemoryScope, MemoryService, type MessageEnvelope, type Modality, type ModelCapabilities, type ModelFilter, type ModelInfo, ModelPlanner, ModelRegistry, type ModelStatus, type NodeToAgentMessage, OMNINODE_VERSION, OPENAI_COMPATIBLE_TYPES, type OmniHilbrasConnectionResult, OmniHilbrasProvider, OmniNodeError, type OmniNodeErrorCode, type OmniNodeErrorOptions, OpenAICompatibleProvider, PROTOCOL_V2, type PipelineConfigYaml, type PipelineDefinition, PipelineEngine, type PipelineEngineOptions, PipelineError, type PipelineRun, type PipelineRunAttempt, type PipelineRunFilter, type PipelineRunOptions, type PipelineRunStatus, type PipelineRunStore, type PipelineStep, type PipelineStepCondition, type PipelineStepConfigYaml, type PipelineStepKind, type PipelineStepRun, type Plan, type PlanFilter, type PlanRequest, type PlanStep, type PlanStore, type PlannerConfigYaml, PlannerError, ProcessAgent, ProcessAgentAdapter, type Project, type ProjectConfigYaml, ProjectStores, type ProtocolAgentToNodeType, ProtocolDecoder, type ProtocolEnvelope, ProtocolError, type ProtocolMessageType, type ProtocolNodeToAgentType, ProtocolSession, type ProtocolViolation, type ProtocolViolationCode, type ProviderAuthentication, type ProviderCapability, type ProviderConfig, type ProviderConfigYaml, ProviderError, type ProviderErrorKind, type ProviderHealth, type ProviderStatus, type ProviderType, type QuestionPayload, type QuestionResponder, type RememberaConfig, RememberaMemoryProvider, type Report, type ReportFilter, type ReportPayload, ReportService, type ReportStore, type RoleConfigYaml, type RoleDefinition, RoleRegistry, type RunPipelineOptions, SUPPORTED_PROTOCOL_VERSIONS, type SessionIds, TERMINAL_TASK_STATUSES, type Task, type TaskContext, TaskEngine, type TaskEngineOptions, TaskError, type TaskPayload, type TaskResult, type TaskStatus, type TaskStore, type TaskStoreFilter, type WritableLike, agentConfigSchema, aggregateReports, appConfigSchema, authHeaders, buildAgentRegistry, buildChildEnv, buildHello, buildHelloAck, buildPipelineEngine, buildPlanner, buildPlannerContext, buildRoleRegistry, composeTaskText, createAgent, createDefaultChatFn, createMemoryProvider, createProvider, createTaskEngine, decodeMessage, decodeStream, defaultProjectConfigYaml, describeExit, encodeMessage, envelope, expandEnvRefs, extractMessage, extractReportFromText, findConfigFile, isNetworkError, isOmniNodeError, isRetryable, isSupportedProtocol, isTimeoutError, keywords, killTree, kindFromStatus, loadConfig, logger, newMessageId, parseHello, parsePlanJson, parseRetryAfter, pipelineConfigSchema, pipelineStepConfigSchema, plannerConfigSchema, projectConfigSchema, providerConfigSchema, providerHttpError, providerTransportError, registryKey, requestJson, requireCompletion, resolveApiKey, resolveEnvPolicy, roleConfigSchema, sanitizeChunk, similar, validateEnvelope, validateWorkingDirectory };
package/dist/index.js CHANGED
@@ -394,56 +394,184 @@ function authHeaders(config, env = process.env) {
394
394
  }
395
395
 
396
396
  // src/providers/http.ts
397
+ var HttpResponseError = class extends Error {
398
+ constructor(message, status, cause) {
399
+ super(message, { cause });
400
+ this.status = status;
401
+ this.name = "HttpResponseError";
402
+ }
403
+ status;
404
+ };
397
405
  var DEFAULT_TIMEOUT_MS = 3e4;
398
406
  async function requestJson(url, options = {}) {
399
407
  const { method = "GET", headers = {}, body, timeoutMs = DEFAULT_TIMEOUT_MS } = options;
400
- let response;
401
- try {
402
- response = await fetch(url, {
403
- method,
404
- headers: {
405
- accept: "application/json",
406
- ...body !== void 0 ? { "content-type": "application/json" } : {},
407
- ...headers
408
- },
409
- ...body !== void 0 ? { body: JSON.stringify(body) } : {},
410
- signal: AbortSignal.timeout(timeoutMs)
411
- });
412
- } catch (error) {
413
- const reason = error instanceof Error && error.name === "TimeoutError" ? `timed out after ${timeoutMs}ms` : error instanceof Error ? error.message : String(error);
414
- throw new ProviderError("PROVIDER_UNAVAILABLE", `Request to ${url} failed: ${reason}.`, {
415
- cause: error
416
- });
417
- }
408
+ const response = await fetch(url, {
409
+ method,
410
+ headers: {
411
+ accept: "application/json",
412
+ ...body !== void 0 ? { "content-type": "application/json" } : {},
413
+ ...headers
414
+ },
415
+ ...body !== void 0 ? { body: JSON.stringify(body) } : {},
416
+ signal: AbortSignal.timeout(timeoutMs)
417
+ });
418
418
  let parsed;
419
419
  try {
420
420
  parsed = await response.json();
421
421
  } catch (error) {
422
- throw new ProviderError("PROVIDER_UNAVAILABLE", `Response from ${url} was not valid JSON.`, {
423
- cause: error,
424
- details: { status: response.status }
425
- });
422
+ throw new HttpResponseError(`Response from ${url} was not valid JSON.`, response.status, error);
426
423
  }
427
424
  return { status: response.status, body: parsed };
428
425
  }
429
426
 
427
+ // src/providers/errors.ts
428
+ function kindToCode(kind) {
429
+ switch (kind) {
430
+ case "AUTHENTICATION_ERROR":
431
+ return "PROVIDER_AUTH_FAILED";
432
+ case "MODEL_NOT_FOUND":
433
+ return "MODEL_NOT_FOUND";
434
+ default:
435
+ return "PROVIDER_UNAVAILABLE";
436
+ }
437
+ }
438
+ function isRetryable(kind) {
439
+ return kind === "RATE_LIMIT_ERROR" || kind === "SERVER_ERROR" || kind === "NETWORK_ERROR";
440
+ }
441
+ function kindFromStatus(status) {
442
+ if (status === 401 || status === 403) return "AUTHENTICATION_ERROR";
443
+ if (status === 429) return "RATE_LIMIT_ERROR";
444
+ if (status === 404) return "MODEL_NOT_FOUND";
445
+ if (status === 408 || status === 504) return "TIMEOUT";
446
+ if (status >= 500) return "SERVER_ERROR";
447
+ if (status >= 400) return "INVALID_REQUEST";
448
+ return "UNKNOWN_ERROR";
449
+ }
450
+ function extractMessage(body) {
451
+ if (body === null || typeof body !== "object") return void 0;
452
+ const root = body;
453
+ const error = root.error ?? root;
454
+ const message = error.message ?? error.detail ?? root.message;
455
+ return typeof message === "string" ? message : void 0;
456
+ }
457
+ function parseRetryAfter(value, now = Date.now()) {
458
+ if (!value) return void 0;
459
+ const seconds = Number(value);
460
+ if (Number.isFinite(seconds)) return Math.max(0, seconds * 1e3);
461
+ const date = Date.parse(value);
462
+ return Number.isNaN(date) ? void 0 : Math.max(0, date - now);
463
+ }
464
+ function isTimeoutError(error) {
465
+ return error instanceof Error && (error.name === "TimeoutError" || error.name === "AbortError") || typeof error?.code === "string" && error.code === "ABORT_ERR";
466
+ }
467
+ function isNetworkError(error) {
468
+ return error instanceof TypeError || typeof error?.cause === "object" && error instanceof Error && !error.code;
469
+ }
470
+ function fallbackMessage(kind, context, status) {
471
+ const { provider, operation } = context;
472
+ switch (kind) {
473
+ case "AUTHENTICATION_ERROR":
474
+ return `Authentication with provider "${provider}" failed (HTTP ${status}). Check that the referenced API key is set and valid.`;
475
+ case "RATE_LIMIT_ERROR":
476
+ return `Provider "${provider}" rate-limited ${operation} (HTTP ${status}). Retry later.`;
477
+ case "MODEL_NOT_FOUND":
478
+ return `Provider "${provider}" does not expose the requested model (HTTP ${status}).`;
479
+ case "TIMEOUT":
480
+ return `Provider "${provider}" timed out during ${operation} (HTTP ${status}).`;
481
+ case "SERVER_ERROR":
482
+ return `Provider "${provider}" returned a server error during ${operation} (HTTP ${status}).`;
483
+ case "INVALID_REQUEST":
484
+ return `Provider "${provider}" rejected ${operation} as invalid (HTTP ${status}).`;
485
+ default:
486
+ return `Provider "${provider}" failed during ${operation} (HTTP ${status}).`;
487
+ }
488
+ }
489
+ function providerHttpError(context) {
490
+ const status = context.status ?? 0;
491
+ const kind = kindFromStatus(status);
492
+ const detail = extractMessage(context.body);
493
+ const retryAfterMs = context.retryAfterMs ?? parseRetryAfter(void 0);
494
+ return new ProviderError(
495
+ context.code ?? kindToCode(kind),
496
+ detail ?? fallbackMessage(kind, context, status),
497
+ {
498
+ cause: context.cause,
499
+ details: {
500
+ kind,
501
+ provider: context.provider,
502
+ operation: context.operation,
503
+ status,
504
+ retryable: isRetryable(kind),
505
+ ...retryAfterMs !== void 0 ? { retryAfterMs } : {},
506
+ ...detail ? { providerMessage: detail } : {}
507
+ }
508
+ }
509
+ );
510
+ }
511
+ function providerTransportError(context) {
512
+ const cause = context.cause;
513
+ const kind = isTimeoutError(cause) ? "TIMEOUT" : cause instanceof SyntaxError || cause?.name === "HttpResponseError" ? "UNKNOWN_ERROR" : "NETWORK_ERROR";
514
+ const message = context.cause instanceof Error ? context.cause.message : String(context.cause);
515
+ return new ProviderError("PROVIDER_UNAVAILABLE", `Provider request failed: ${message}`, {
516
+ cause: context.cause,
517
+ details: {
518
+ kind,
519
+ provider: context.provider,
520
+ operation: context.operation,
521
+ retryable: isRetryable(kind)
522
+ }
523
+ });
524
+ }
525
+
430
526
  // src/providers/openai-compatible/index.ts
431
527
  var OpenAICompatibleProvider = class {
432
528
  config;
529
+ providerId;
530
+ authentication;
433
531
  log;
434
- constructor(config, log = logger) {
532
+ /** Discovered models, so getModel() answers without another round-trip. */
533
+ discovered;
534
+ env;
535
+ constructor(config, log = logger, env = process.env) {
435
536
  this.config = { ...config, baseUrl: normalizeBaseUrl(config.baseUrl) };
537
+ this.env = env;
538
+ this.providerId = config.providerId ?? config.name;
539
+ this.authentication = {
540
+ method: config.apiKeyEnvVar ? "bearer" : "none",
541
+ ...config.apiKeyEnvVar ? { envVar: config.apiKeyEnvVar } : {},
542
+ configured: config.apiKeyEnvVar ? Boolean(env[config.apiKeyEnvVar]) : true
543
+ };
436
544
  this.log = log.child({ provider: config.name });
437
545
  }
546
+ /** Authenticate + discover (roadmap §10, Model Discovery). */
547
+ async connect() {
548
+ authHeaders(this.config, this.env);
549
+ const models = await this.listModels();
550
+ this.discovered = models;
551
+ return models;
552
+ }
553
+ async getModel(modelId) {
554
+ const models = this.discovered ?? await this.connect();
555
+ return models.find((m) => m.id === modelId);
556
+ }
438
557
  async listModels() {
439
- const { status, body } = await requestJson(`${this.config.baseUrl}/models`, {
440
- headers: authHeaders(this.config)
441
- });
442
- if (status === 401 || status === 403) {
443
- throw authFailed(this.config, status);
558
+ let response;
559
+ try {
560
+ response = await requestJson(`${this.config.baseUrl}/models`, {
561
+ headers: authHeaders(this.config, this.env)
562
+ });
563
+ } catch (error) {
564
+ if (error instanceof ProviderError) throw error;
565
+ throw providerTransportError({ provider: this.config.name, operation: "listModels", cause: error });
444
566
  }
567
+ const { status, body } = response;
445
568
  if (status !== 200) {
446
- throw unavailable(this.config, `Model discovery returned HTTP ${status}.`, status);
569
+ throw providerHttpError({
570
+ provider: this.config.name,
571
+ operation: "listModels",
572
+ status,
573
+ body
574
+ });
447
575
  }
448
576
  const entries = extractModelEntries(body, this.config);
449
577
  const models = [];
@@ -453,22 +581,40 @@ var OpenAICompatibleProvider = class {
453
581
  skipped += 1;
454
582
  continue;
455
583
  }
456
- models.push({
457
- provider: this.config.name,
458
- id: entry.id,
459
- displayName: entry.id,
460
- status: "available",
461
- metadata: {
462
- ...typeof entry.owned_by === "string" ? { ownedBy: entry.owned_by } : {},
463
- ...typeof entry.created === "number" ? { createdAt: entry.created } : {}
464
- }
465
- });
584
+ models.push(this.toModelInfo(entry));
466
585
  }
467
586
  if (skipped > 0) {
468
587
  this.log.warn(`Skipped ${skipped} model entr(y/ies) without a valid id.`);
469
588
  }
470
589
  return models;
471
590
  }
591
+ /** Maps a raw model entry onto the normalized metadata shape (§10). */
592
+ toModelInfo(entry) {
593
+ const parameters = Array.isArray(entry.supported_parameters) ? entry.supported_parameters.filter((p) => typeof p === "string") : void 0;
594
+ const contextWindow = typeof entry.context_length === "number" ? entry.context_length : typeof entry.context_window === "number" ? entry.context_window : void 0;
595
+ const inputTypes = normalizeModalities(entry.input_modalities);
596
+ const outputTypes = normalizeModalities(entry.output_modalities);
597
+ return {
598
+ provider: this.config.name,
599
+ id: entry.id,
600
+ displayName: entry.id,
601
+ ...typeof entry.name === "string" ? { name: entry.name } : {},
602
+ status: "available",
603
+ ...contextWindow !== void 0 ? { contextWindow } : {},
604
+ ...inputTypes !== void 0 ? { inputTypes } : {},
605
+ ...outputTypes !== void 0 ? { outputTypes } : {},
606
+ ...parameters !== void 0 ? {
607
+ supportsTools: parameters.includes("tools") || parameters.includes("tool_choice"),
608
+ supportsStructuredOutput: parameters.includes("response_format"),
609
+ supportsStreaming: parameters.includes("stream")
610
+ } : {},
611
+ metadata: {
612
+ ...typeof entry.owned_by === "string" ? { ownedBy: entry.owned_by } : {},
613
+ ...typeof entry.created === "number" ? { createdAt: entry.created } : {},
614
+ ...parameters !== void 0 ? { supportedParameters: parameters } : {}
615
+ }
616
+ };
617
+ }
472
618
  async healthCheck() {
473
619
  const lastCheckedAt = (/* @__PURE__ */ new Date()).toISOString();
474
620
  try {
@@ -498,27 +644,34 @@ var OpenAICompatibleProvider = class {
498
644
  }
499
645
  }
500
646
  async chat(request) {
501
- const { status, body } = await requestJson(`${this.config.baseUrl}/chat/completions`, {
502
- method: "POST",
503
- headers: authHeaders(this.config),
504
- body: {
505
- model: request.model,
506
- messages: request.messages,
507
- ...request.temperature !== void 0 ? { temperature: request.temperature } : {},
508
- ...request.maxTokens !== void 0 ? { max_tokens: request.maxTokens } : {},
509
- ...request.stop !== void 0 ? { stop: request.stop } : {}
510
- }
511
- });
512
- if (status === 401 || status === 403) throw authFailed(this.config, status);
513
- if (status === 404) {
514
- throw new ProviderError(
515
- "MODEL_NOT_FOUND",
516
- `Model "${request.model}" or the chat endpoint was not found on provider "${this.config.name}" (HTTP 404).`,
517
- { details: { status } }
518
- );
647
+ let response;
648
+ try {
649
+ response = await requestJson(`${this.config.baseUrl}/chat/completions`, {
650
+ method: "POST",
651
+ headers: authHeaders(this.config, this.env),
652
+ body: {
653
+ model: request.model,
654
+ messages: request.messages,
655
+ ...request.temperature !== void 0 ? { temperature: request.temperature } : {},
656
+ ...request.maxTokens !== void 0 ? { max_tokens: request.maxTokens } : {},
657
+ ...request.stop !== void 0 ? { stop: request.stop } : {}
658
+ }
659
+ });
660
+ } catch (error) {
661
+ if (error instanceof ProviderError) throw error;
662
+ throw providerTransportError({ provider: this.config.name, operation: "chat", cause: error });
519
663
  }
664
+ const { status, body } = response;
520
665
  if (status !== 200) {
521
- throw unavailable(this.config, `Chat completion returned HTTP ${status}.`, status);
666
+ throw providerHttpError({
667
+ provider: this.config.name,
668
+ operation: "chat",
669
+ status,
670
+ body,
671
+ ...status === 404 ? {
672
+ code: "MODEL_NOT_FOUND"
673
+ } : {}
674
+ });
522
675
  }
523
676
  const payload = body;
524
677
  const choices = Array.isArray(payload.choices) ? payload.choices : [];
@@ -540,6 +693,13 @@ var OpenAICompatibleProvider = class {
540
693
  };
541
694
  }
542
695
  };
696
+ function normalizeModalities(value) {
697
+ const list = Array.isArray(value) ? value : typeof value === "string" ? [value] : void 0;
698
+ if (!list) return void 0;
699
+ const allowed = /* @__PURE__ */ new Set(["text", "image", "audio", "video", "embedding"]);
700
+ const modalities = list.filter((m) => typeof m === "string" && allowed.has(m));
701
+ return modalities.length > 0 ? modalities : void 0;
702
+ }
543
703
  function normalizeBaseUrl(baseUrl) {
544
704
  return baseUrl.replace(/\/+$/, "");
545
705
  }
@@ -554,14 +714,6 @@ function extractModelEntries(body, config) {
554
714
  { details: { endpoint: "models" } }
555
715
  );
556
716
  }
557
- function authFailed(config, status) {
558
- const envHint = config.apiKeyEnvVar ? ` Check that $${config.apiKeyEnvVar} is set and valid.` : " The provider requires authentication; configure api_key_env_var for it.";
559
- return new ProviderError(
560
- "PROVIDER_AUTH_FAILED",
561
- `Authentication with provider "${config.name}" failed (HTTP ${status}).${envHint}`,
562
- { details: { status } }
563
- );
564
- }
565
717
  function unavailable(config, message, status) {
566
718
  return new ProviderError("PROVIDER_UNAVAILABLE", `Provider "${config.name}": ${message}`, {
567
719
  details: { status }
@@ -578,7 +730,7 @@ var OmniHilbrasProvider = class extends OpenAICompatibleProvider {
578
730
  * Run the full connect flow. Throws on authentication or transport failure
579
731
  * (use healthCheck() for a non-throwing probe).
580
732
  */
581
- async connect(registry) {
733
+ async connectAndRegister(registry) {
582
734
  const apiKey = resolveApiKey(this.config);
583
735
  const discovered = await this.listModels();
584
736
  const seen = /* @__PURE__ */ new Set();
@@ -3325,7 +3477,7 @@ var ProjectStores = class {
3325
3477
  };
3326
3478
 
3327
3479
  // src/version.ts
3328
- var OMNINODE_VERSION = "2.0.0-alpha.5";
3480
+ var OMNINODE_VERSION = "2.0.0-alpha.6";
3329
3481
  export {
3330
3482
  AgentAdapterRegistry,
3331
3483
  AgentError,
@@ -3395,22 +3547,30 @@ export {
3395
3547
  encodeMessage,
3396
3548
  envelope,
3397
3549
  expandEnvRefs,
3550
+ extractMessage,
3398
3551
  extractReportFromText,
3399
3552
  findConfigFile,
3553
+ isNetworkError,
3400
3554
  isOmniNodeError,
3555
+ isRetryable,
3401
3556
  isSupportedProtocol,
3557
+ isTimeoutError,
3402
3558
  keywords,
3403
3559
  killTree,
3560
+ kindFromStatus,
3404
3561
  loadConfig,
3405
3562
  logger,
3406
3563
  newMessageId,
3407
3564
  parseHello,
3408
3565
  parsePlanJson,
3566
+ parseRetryAfter,
3409
3567
  pipelineConfigSchema,
3410
3568
  pipelineStepConfigSchema,
3411
3569
  plannerConfigSchema,
3412
3570
  projectConfigSchema,
3413
3571
  providerConfigSchema,
3572
+ providerHttpError,
3573
+ providerTransportError,
3414
3574
  registryKey,
3415
3575
  requestJson,
3416
3576
  requireCompletion,