@2tle/pi-provider-manager 0.1.2 → 0.1.3

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@2tle/pi-provider-manager",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "description": "Manage OpenAI-compatible providers and model catalogs in Pi Agent",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
package/src/models.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import {
2
2
  API,
3
+ CODEX_API,
3
4
  DEFAULT_CONTEXT_WINDOW,
4
5
  DEFAULT_MAX_TOKENS,
5
6
  PI_THINKING_LEVELS,
@@ -25,7 +26,8 @@ function getReasoningEfforts(value: unknown): Set<string> {
25
26
  if (!Array.isArray(value)) return new Set();
26
27
  return new Set(value.flatMap((item) => {
27
28
  if (typeof item === "string") return [item.toLowerCase()];
28
- const effort = asNonEmptyString(asRecord(item)?.value);
29
+ const record = asRecord(item);
30
+ const effort = asNonEmptyString(record?.value ?? record?.effort);
29
31
  return effort ? [effort.toLowerCase()] : [];
30
32
  }));
31
33
  }
@@ -48,18 +50,20 @@ export function modelFromPayload(provider: StoredProvider, payload: OpenAIModelP
48
50
  const override = provider.modelOverrides?.[id];
49
51
  const capabilities = asRecord(payload.capabilities);
50
52
  const cost = asRecord(payload.cost);
51
- const inputModalities = Array.isArray(payload.input) ? payload.input : capabilities?.input_modalities;
53
+ const inputModalities = Array.isArray(payload.input) ? payload.input : payload.input_modalities ?? capabilities?.input_modalities;
52
54
  const input: ("text" | "image")[] = Array.isArray(inputModalities) && inputModalities.includes("image")
53
55
  ? ["text", "image"]
54
56
  : ["text"];
55
- const efforts = getReasoningEfforts(payload.reasoning_efforts ?? capabilities?.reasoning_effort);
57
+ const efforts = getReasoningEfforts(payload.reasoning_efforts ?? payload.supported_reasoning_levels ?? capabilities?.reasoning_effort);
56
58
  const reasoning = payload.reasoning === true || payload.supports_reasoning === true ||
57
59
  payload.supports_reasoning_effort === true || capabilities?.supports_reasoning === true || efforts.size > 0;
58
60
 
61
+ const isCodex = provider.baseUrl.trim().replace(/\/+$/, "").endsWith("/codex");
62
+
59
63
  return {
60
64
  id,
61
65
  name: asNonEmptyString(payload.name) ?? id,
62
- api: API,
66
+ api: isCodex ? CODEX_API : API,
63
67
  reasoning,
64
68
  ...(reasoning ? { thinkingLevelMap: thinkingLevelMap(efforts), compat: { supportsReasoningEffort: true } } : {}),
65
69
  input,
package/src/provider.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import type { RefreshModelsContext } from "@earendil-works/pi-ai";
2
2
  import { modelFromPayload } from "./models.js";
3
- import type { ManagedProviderConfig, OpenAIModelsPayload, ProviderModelConfig, StoredProvider } from "./types.js";
3
+ import { CODEX_API } from "./types.js";
4
+ import type { ManagedProviderConfig, OpenAIModelsPayload, OpenAIModelPayload, ProviderModelConfig, StoredProvider } from "./types.js";
4
5
 
5
6
  function asRecord(value: unknown): Record<string, unknown> | undefined {
6
7
  return typeof value === "object" && value !== null ? (value as Record<string, unknown>) : undefined;
@@ -28,11 +29,16 @@ export async function fetchProviderModels(
28
29
  if (!response.ok) throw new Error(`Failed to fetch model list (${response.status} ${response.statusText})`);
29
30
 
30
31
  const payload = (await response.json()) as OpenAIModelsPayload | unknown[];
31
- const data = Array.isArray(payload) ? payload : asRecord(payload)?.data;
32
- if (!Array.isArray(data)) throw new Error("Model list response does not contain a data array.");
32
+ const data = Array.isArray(payload) ? payload : asRecord(payload)?.data ?? asRecord(payload)?.models;
33
+ if (!Array.isArray(data)) throw new Error("Model list response does not contain a data/models array.");
33
34
  return data.map((item) => {
34
35
  const record = asRecord(item);
35
- return record ? modelFromPayload(provider, record) : undefined;
36
+ if (!record) return undefined;
37
+ // Codex's catalog uses slug/display_name instead of OpenAI's id/name.
38
+ const normalized: OpenAIModelPayload = provider.baseUrl.trim().replace(/\/+$/, "").endsWith("/codex")
39
+ ? { ...record, id: record.id ?? record.slug, name: record.name ?? record.display_name }
40
+ : record;
41
+ return modelFromPayload(provider, normalized);
36
42
  }).filter((model): model is ProviderModelConfig => model !== undefined);
37
43
  }
38
44
 
@@ -40,10 +46,13 @@ export function createManagedProvider(
40
46
  config: StoredProvider,
41
47
  getApiKey: () => string | undefined,
42
48
  ): ManagedProviderConfig {
49
+ const isCodex = config.baseUrl.trim().replace(/\/+$/, "").endsWith("/codex");
43
50
  return {
44
51
  name: config.name,
45
52
  baseUrl: config.baseUrl,
46
- api: "openai-completions",
53
+ // Codex gateways expose Responses at /codex/responses; using the
54
+ // completions transport would POST /codex/chat/completions (405).
55
+ api: isCodex ? CODEX_API : "openai-completions",
47
56
  apiKey: getApiKey() ?? "local",
48
57
  models: [],
49
58
  refreshModels: (context) => fetchProviderModels(config, context, getApiKey),
package/src/types.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import type { RefreshModelsContext } from "@earendil-works/pi-ai";
2
2
 
3
3
  export const API = "openai-completions" as const;
4
+ export const CODEX_API = "openai-responses" as const;
4
5
  export const DEFAULT_CONTEXT_WINDOW = 128_000;
5
6
  export const DEFAULT_MAX_TOKENS = 16_384;
6
7
  export const REFRESH_TIMEOUT_MS = 30_000;
@@ -31,7 +32,7 @@ export interface StoredSecrets {
31
32
  export interface ProviderModelConfig {
32
33
  id: string;
33
34
  name: string;
34
- api: typeof API;
35
+ api: typeof API | typeof CODEX_API;
35
36
  reasoning: boolean;
36
37
  thinkingLevelMap?: Partial<Record<PiThinkingLevel, string | null>>;
37
38
  compat?: {
@@ -51,7 +52,7 @@ export interface ProviderModelConfig {
51
52
  export interface ManagedProviderConfig {
52
53
  name: string;
53
54
  baseUrl: string;
54
- api: typeof API;
55
+ api: typeof API | typeof CODEX_API;
55
56
  apiKey: string;
56
57
  models: ProviderModelConfig[];
57
58
  refreshModels(context: RefreshModelsContext): Promise<ProviderModelConfig[]>;
@@ -68,11 +69,14 @@ export interface OpenAIModelPayload {
68
69
  supports_reasoning?: unknown;
69
70
  supports_reasoning_effort?: unknown;
70
71
  reasoning_efforts?: unknown;
72
+ supported_reasoning_levels?: unknown;
71
73
  input?: unknown;
74
+ input_modalities?: unknown;
72
75
  cost?: unknown;
73
76
  capabilities?: unknown;
74
77
  }
75
78
 
76
79
  export interface OpenAIModelsPayload {
77
80
  data?: unknown;
81
+ models?: unknown;
78
82
  }
Binary file