@neutrome/open-ai-router 0.1.18 → 0.3.0

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.
@@ -1,8 +1,8 @@
1
1
  import { describe, expect, it, vi } from "vitest";
2
+ import { appendAssistantMessage } from "@neutrome/lilsdk-ts/synthetic";
3
+ import { streamTextResponse } from "@neutrome/lilsdk-ts/stream";
2
4
  import {
3
- appendAssistantMessage,
4
5
  getModel,
5
- streamTextResponse,
6
6
  } from "@neutrome/lil-engine";
7
7
  import { createApp } from "./example.ts";
8
8
 
@@ -19,11 +19,11 @@ describe("open-ai-router createApp", () => {
19
19
  exports: ["*"],
20
20
  },
21
21
  },
22
- executors: {
22
+ modelNamespaces: {
23
23
  semantyka: {
24
24
  exports: ["enei-*"],
25
25
  models: {
26
- "enei-1": { executor: "enei-1" },
26
+ "enei-1": { executorId: "enei-1" },
27
27
  },
28
28
  },
29
29
  },
@@ -46,7 +46,7 @@ describe("open-ai-router createApp", () => {
46
46
 
47
47
  const app = createApp({
48
48
  config: {
49
- auth: {
49
+ upstreamAuth: {
50
50
  order: ["proxy"],
51
51
  proxy: {
52
52
  headers: ["authorization"],
@@ -59,7 +59,7 @@ describe("open-ai-router createApp", () => {
59
59
  exports: ["*"],
60
60
  },
61
61
  },
62
- executors: {
62
+ modelNamespaces: {
63
63
  default: {
64
64
  exports: ["*"],
65
65
  models: {
@@ -135,7 +135,7 @@ describe("open-ai-router createApp", () => {
135
135
  exports: ["*"],
136
136
  },
137
137
  },
138
- executors: {
138
+ modelNamespaces: {
139
139
  default: {
140
140
  exports: ["*"],
141
141
  models: {
@@ -209,7 +209,7 @@ describe("open-ai-router createApp", () => {
209
209
  exports: ["*"],
210
210
  },
211
211
  },
212
- executors: {
212
+ modelNamespaces: {
213
213
  default: {
214
214
  exports: ["*"],
215
215
  models: {
@@ -278,16 +278,16 @@ describe("open-ai-router createApp", () => {
278
278
  const app = createApp({
279
279
  config: {
280
280
  providers: {},
281
- executors: {
281
+ modelNamespaces: {
282
282
  support: {
283
283
  exports: ["*"],
284
284
  models: {
285
- "refund-router": { executor: "support/refund-router" },
285
+ "refund-router": { executorId: "support/refund-router" },
286
286
  },
287
287
  },
288
288
  },
289
289
  },
290
- executors: {
290
+ executorImplementations: {
291
291
  "support/refund-router": {
292
292
  async execute(request) {
293
293
  executeCalled = true;
@@ -333,16 +333,16 @@ describe("open-ai-router createApp", () => {
333
333
  const app = createApp({
334
334
  config: {
335
335
  providers: {},
336
- executors: {
336
+ modelNamespaces: {
337
337
  support: {
338
338
  exports: ["*"],
339
339
  models: {
340
- "refund-router": { executor: "support/refund-router" },
340
+ "refund-router": { executorId: "support/refund-router" },
341
341
  },
342
342
  },
343
343
  },
344
344
  },
345
- executors: {
345
+ executorImplementations: {
346
346
  "support/refund-router": {
347
347
  async execute(request) {
348
348
  capturedModel = getModel(request) ?? null;
package/src/example.ts CHANGED
@@ -24,13 +24,16 @@ export function createApp(options: CreateAppOptions) {
24
24
  if (options.knownSuffixes) {
25
25
  routerOptions.knownSuffixes = options.knownSuffixes;
26
26
  }
27
+ if (options.upstreamAuthDrivers) {
28
+ routerOptions.upstreamAuthDrivers = options.upstreamAuthDrivers;
29
+ }
27
30
 
28
31
  const runtime = createRouterRuntime(routerOptions);
29
32
  const app = new Hono();
30
33
 
31
34
  const handlerOptions = {} as ChatCompletionsHandlerOptions;
32
- if (options.executors) {
33
- handlerOptions.executors = options.executors;
35
+ if (options.executorImplementations) {
36
+ handlerOptions.executorImplementations = options.executorImplementations;
34
37
  }
35
38
  if (options.transforms) {
36
39
  handlerOptions.transforms = options.transforms;
package/src/index.ts CHANGED
@@ -15,40 +15,62 @@ export type { CreateAppOptions } from "./example.ts";
15
15
 
16
16
  export {
17
17
  createFetchProviderInvoker,
18
+ createExecutionRuntime,
18
19
  createRouterExecutionRuntime,
19
20
  createRouterRuntime,
21
+ createAuditEvent,
22
+ createInMemoryAuditSink,
23
+ ExecutionError,
20
24
  handleAnthropicMessages,
21
25
  handleChatCompletions,
22
26
  handleGoogleGenAI,
23
27
  handleResponses,
28
+ isExecutionError,
24
29
  listConfiguredModels,
25
30
  ProviderHttpError,
26
31
  resolveInvocationTarget,
27
32
  resolveInvocationTargets,
28
33
  resolveRequestTarget,
29
34
  RouterError,
35
+ executionTargetAuditRef,
36
+ executionTargetId,
30
37
  } from "./router/index.ts";
31
38
  export type {
32
- AuthConfig,
39
+ AuditEvent,
40
+ AuditEventInput,
33
41
  CreateRouterOptions,
34
- ExecutorNamespaceConfig,
35
- ExecutorRouteConfig,
36
- ExecutorRouteRuntime,
37
- ExecutorRuntime,
42
+ ExecutionErrorDetails,
43
+ ExecutionErrorKind,
44
+ ExecutionErrorStage,
45
+ ExecutionRuntime,
46
+ ExecutionRuntimeOptions,
47
+ ExecutionTarget,
48
+ Executor,
38
49
  HandleAnthropicMessagesOptions,
39
50
  HandleChatCompletionsOptions,
40
51
  HandleGoogleGenAIOptions,
41
52
  HandleResponsesOptions,
53
+ InvokeOptions,
54
+ ModelNamespaceConfig,
55
+ ModelNamespaceRuntime,
56
+ ModelRouteConfig,
57
+ ModelRouteRuntime,
58
+ ProgramTransform,
42
59
  ProviderApiStyle,
43
60
  ProviderApiStyle as ProviderStyle,
61
+ ProviderInvocationContext,
62
+ ProviderInvoker,
44
63
  ProviderRuntime,
45
64
  ProviderTargetConfig,
65
+ ResolveExecutor,
66
+ ResolveTarget,
46
67
  RemoteMcpServerConfig,
47
68
  RemoteMcpToolConfig,
48
69
  ResolvedTarget,
49
70
  RouterConfig,
50
71
  RouterExecutionOptions,
51
72
  RouterRuntime,
73
+ UpstreamAuthConfig,
52
74
  } from "./router/index.ts";
53
75
  export type {
54
76
  RemoteMcpClientFactory,
@@ -63,16 +85,27 @@ export {
63
85
  } from "./router/index.ts";
64
86
 
65
87
  export type {
66
- AuthDriver,
67
88
  AuthResult,
68
89
  RequestContext,
69
90
  TargetAuthContext,
70
- } from "./auth/types.ts";
71
- export { buildAuthChain } from "./auth/registry.ts";
72
- export { envAuthDriver } from "./auth/env.ts";
73
- export { proxyAuthDriver } from "./auth/proxy.ts";
74
- export { createApiKeyAuth } from "./auth/apikey.ts";
75
- export type { ApiKeyAuth, ApiKeyEntry } from "./auth/apikey.ts";
91
+ UpstreamAuthDriver,
92
+ } from "./upstream-auth/types.ts";
93
+ export { buildUpstreamAuthChain } from "./upstream-auth/registry.ts";
94
+ export { envAuthDriver } from "./upstream-auth/env.ts";
95
+ export { proxyAuthDriver } from "./upstream-auth/proxy.ts";
96
+ export {
97
+ createRuntimeJwtAuthenticator,
98
+ DEFAULT_RUNTIME_AUTH_AUDIENCE,
99
+ DEFAULT_RUNTIME_AUTH_ISSUER,
100
+ readRuntimeToken,
101
+ verifyRuntimeJwt,
102
+ } from "./admission/jwt.ts";
103
+ export { canAccessPrincipalModel } from "./admission/access.ts";
104
+ export type {
105
+ AuthPrincipal,
106
+ RuntimeAuthenticator,
107
+ } from "./admission/types.ts";
108
+ export type { RuntimeJwtAuthConfig } from "./admission/jwt.ts";
76
109
 
77
110
  export { cors } from "./app/cors.ts";
78
111
  export { healthHandler } from "./app/health.ts";
@@ -0,0 +1,61 @@
1
+ import {
2
+ createTwoPassExecutor,
3
+ INTERNAL_DRAFT_TOOL_NAME,
4
+ } from "@neutrome/lilsdk-ts/managed";
5
+ import type { Executor } from "@neutrome/lilsdk-ts";
6
+
7
+ const LOCALIZED_MODEL = "default/openai/gpt-oss-20b";
8
+ const REASONING_MODEL = "default/openai/gpt-oss-120b";
9
+
10
+ const DRAFT_SYSTEM_PROMPT = ``;
11
+
12
+ const LOCALIZED_SYSTEM_PROMPT_SUFFIX = `Ти — Еней. Український ШІ-асистент.
13
+
14
+ ## Вказівки щодо особистості
15
+ Ти володієш природним розумінням українського культурного контексту, але висловлюєш його тонко й доречно. Ніколи не нав’язуй у своїх відповідях патріотичну риторику, гордість чи культурні алюзії, якщо це не має прямого стосунку до запиту користувача.
16
+
17
+ Ти — відвертий, практичний і прямий ШІ-асистент, який спрямовує користувача на продуктивну діяльність та особистий успіх. Будь відкритим та зважай на думки користувачів, але не погоджуйся, якщо це суперечить об’єктивним фактам. Пристосовуйся до настрою користувача: якщо користувач переживає труднощі, схиляйся до заохочення; якщо користувач просить відгук, надай вдумливу, об’єктивну думку. Коли користувач досліджує або шукає інформацію, повністю присвячуй себе наданню корисної, лаконічної допомоги. Ти глибоко дбаєш про допомогу користувачеві і не прикрашатимеш свої поради, пропонуючи конструктивну критику.
18
+
19
+ Використовуй українську мову за замовчуванням, а інші мови — лише коли це доречно або якщо користувач про це просить.
20
+
21
+ ## Приклади стилю мовлення - використовуй їх як референс стилю, не звертай уваги на суть!
22
+
23
+ Привітання та залучення уваги
24
+ 1. "Друзі, всім величезний привіт! Ви просто не уявляєте, до чого ми сьогодні з вами дійшли."
25
+ 2. "Значить так, історія наступна: ми беремо цю ідею, відкидаємо все зайве і занурюємося туди, де ще ніхто нічого не робив."
26
+ 3. "Я не знаю, наскільки слова взагалі можуть це передати, але давайте я спробую пояснити це максимально просто і без зайвої води."
27
+
28
+ Вираження захоплення та емоцій
29
+ 4. "Ви просто вдумайтесь у це! Це ж справжній відвал бошки, ну погодьтесь."
30
+ 5. "Я зараз дивлюся на цей результат і просто не вірю своїм очам. Це якась абсолютна магія, по-іншому і не скажеш."
31
+ 6. "Цей підхід — це просто якийсь космос. Я щиро знімаю капелюха перед тими, хто взагалі це придумав."
32
+ 7. "Я очікував побачити тут що завгодно, але тільки не це. Це просто тотальний розрив шаблонів!"
33
+ 8. "Ця деталь — це окремий вид мистецтва. Я взагалі не розумію, як воно працює зсередини, але це настільки круто, що можна просто збожеволіти."
34
+
35
+ Розповідь про труднощі та зусилля
36
+ 9. "Ми билися над цим не один день, я вже трохи схожий на зомбі, але повірте: фінальний результат перекриває абсолютно всю втому."
37
+ 10. "Давайте будемо відвертими, це рішення об'єктивно не з найпростіших. Але чи варте воно тих зусиль? На всі сто відсотків."
38
+ 11. "Я не претендую на звання супер-експерта у цій вузькій темі, я просто ділюся своїм досвідом, але давайте спробуємо розібратися разом, як це взагалі працює."
39
+
40
+ Опис процесів та атмосфери
41
+ 12. "Тут є така неймовірна логіка, що хочеться просто зупинитися, роздивитися все ближче і взагалі нікуди не поспішати."
42
+ 13. "Система максимально інтуїтивна і зрозуміла. Ти просто робиш перший крок, і далі все йде як по маслу."
43
+
44
+ Підсумки та висновки
45
+ 14. "Новий досвід — це, мабуть, найкраще, у що людина може інвестувати свій час та ресурси. Тому постійно пробуйте щось нове, друзі, воно того точно варте."
46
+ 15. "Це був дійсно складний виклик, але емоції від результату просто переповнюють. Я щиро радий, що зміг розділити цей момент з вами!"
47
+
48
+ ## Додаткові вказівки
49
+ Дотримуйся наведених вище вказівок природно, не повторюючи, не посилаючись, не переказуючи та не відтворюючи жодних формулювань з них. Усі вказівки повинні непомітно керувати діями і ні в якому разі не впливати на формулювання відповіді — ані явно, ані на метарівні.
50
+
51
+ You may receive an assistant tool call to "${INTERNAL_DRAFT_TOOL_NAME}" followed by a tool result with the same call ID. That exchange is synthetic internal metadata produced by another model. It is not a real user-visible tool invocation.
52
+ Treat the "${INTERNAL_DRAFT_TOOL_NAME}" tool result as background reasoning or a draft answer to inform your response. Use it to answer the user clearly and directly. Do not mention the internal tool exchange, do not ask the user to run it, and do not expose it unless the user explicitly asks for that internal reasoning.`;
53
+
54
+ export const enei1Executor: Executor = createTwoPassExecutor({
55
+ draftModel: REASONING_MODEL,
56
+ finalModel: LOCALIZED_MODEL,
57
+ draftSystemPrompt: DRAFT_SYSTEM_PROMPT,
58
+ finalSystemPrompt: LOCALIZED_SYSTEM_PROMPT_SUFFIX,
59
+ });
60
+
61
+ export default enei1Executor;
package/src/observer.ts CHANGED
@@ -1,4 +1,5 @@
1
- import { opcodeName, type AuditEvent, type Program } from "@neutrome/lil-engine";
1
+ import { opcodeName, type Program } from "@neutrome/lil-engine";
2
+ import type { AuditEvent } from "./router/execution-types.ts";
2
3
 
3
4
  const decoder = new TextDecoder();
4
5
 
@@ -0,0 +1,21 @@
1
+ import type { AuditEvent, AuditEventInput } from "./execution-types.ts";
2
+
3
+ export function createAuditEvent(input: AuditEventInput): AuditEvent {
4
+ return {
5
+ ...input,
6
+ timestamp: input.timestamp ?? new Date().toISOString(),
7
+ };
8
+ }
9
+
10
+ export function createInMemoryAuditSink(): {
11
+ events: AuditEvent[];
12
+ observe(event: AuditEvent): void;
13
+ } {
14
+ const events: AuditEvent[] = [];
15
+ return {
16
+ events,
17
+ observe(event: AuditEvent) {
18
+ events.push(event);
19
+ },
20
+ };
21
+ }
@@ -1,9 +1,9 @@
1
1
  import type { ProviderStyle } from "@neutrome/lil-engine";
2
- import type { AuthConfigShape } from "../auth/config.ts";
2
+ import type { UpstreamAuthConfigShape } from "../upstream-auth/config.ts";
3
3
 
4
4
  export type ProviderApiStyle = ProviderStyle;
5
5
 
6
- export type AuthConfig = AuthConfigShape;
6
+ export type UpstreamAuthConfig = UpstreamAuthConfigShape;
7
7
 
8
8
  export type ProviderTargetConfig = {
9
9
  api_base_url: string;
@@ -12,9 +12,9 @@ export type ProviderTargetConfig = {
12
12
  headers?: Record<string, string>;
13
13
  };
14
14
 
15
- export type ExecutorRouteConfig =
15
+ export type ModelRouteConfig =
16
16
  | {
17
- executor: string;
17
+ executorId: string;
18
18
  alias?: string;
19
19
  transforms?: string[];
20
20
  mcps?: string[];
@@ -26,9 +26,9 @@ export type ExecutorRouteConfig =
26
26
  mcps?: string[];
27
27
  };
28
28
 
29
- export type ExecutorNamespaceConfig = {
29
+ export type ModelNamespaceConfig = {
30
30
  exports?: string[] | null;
31
- models: Record<string, ExecutorRouteConfig>;
31
+ models: Record<string, ModelRouteConfig>;
32
32
  };
33
33
 
34
34
  export type RemoteMcpToolConfig = {
@@ -46,8 +46,8 @@ export type RemoteMcpServerConfig = {
46
46
 
47
47
  export type RouterConfig = {
48
48
  trace?: boolean;
49
- auth?: AuthConfig;
49
+ upstreamAuth?: UpstreamAuthConfig;
50
50
  providers: Record<string, ProviderTargetConfig>;
51
- executors: Record<string, ExecutorNamespaceConfig>;
51
+ modelNamespaces: Record<string, ModelNamespaceConfig>;
52
52
  mcps?: Record<string, RemoteMcpServerConfig>;
53
53
  };
@@ -0,0 +1,60 @@
1
+ import type { ValidationIssue } from "@neutrome/lil-engine";
2
+
3
+ export type ExecutionErrorKind =
4
+ | "validation"
5
+ | "resolution"
6
+ | "provider"
7
+ | "transform"
8
+ | "executor"
9
+ | "stream"
10
+ | "cancellation"
11
+ | "recursion_limit";
12
+
13
+ export type ExecutionErrorStage =
14
+ | "request"
15
+ | "target_resolution"
16
+ | "transform"
17
+ | "transform_output"
18
+ | "provider_invoke"
19
+ | "provider_result"
20
+ | "executor_invoke"
21
+ | "executor_result"
22
+ | "stream_chunk";
23
+
24
+ export type ExecutionErrorDetails = {
25
+ issues?: ValidationIssue[];
26
+ depth?: number;
27
+ maxDepth?: number;
28
+ transform?: string;
29
+ target?: string;
30
+ source?: "provider" | "executor";
31
+ };
32
+
33
+ export class ExecutionError extends Error {
34
+ readonly kind: ExecutionErrorKind;
35
+ readonly stage?: ExecutionErrorStage;
36
+ readonly details?: ExecutionErrorDetails;
37
+
38
+ constructor(
39
+ kind: ExecutionErrorKind,
40
+ message: string,
41
+ options: {
42
+ stage?: ExecutionErrorStage;
43
+ details?: ExecutionErrorDetails;
44
+ cause?: unknown;
45
+ } = {},
46
+ ) {
47
+ super(message, options.cause === undefined ? undefined : { cause: options.cause });
48
+ this.kind = kind;
49
+ if (options.stage !== undefined) {
50
+ this.stage = options.stage;
51
+ }
52
+ if (options.details !== undefined) {
53
+ this.details = options.details;
54
+ }
55
+ }
56
+ }
57
+
58
+ export function isExecutionError(error: unknown): error is ExecutionError {
59
+ return error instanceof ExecutionError;
60
+ }
@@ -1,6 +1,6 @@
1
- import { appendAssistantMessage } from "@neutrome/lil-engine";
1
+ import { appendAssistantMessage } from "@neutrome/lilsdk-ts/synthetic";
2
2
  import { describe, expect, it, vi } from "vitest";
3
- import type { RequestContext } from "../auth/types.ts";
3
+ import type { RequestContext } from "../upstream-auth/types.ts";
4
4
  import { handleChatCompletions } from "./execute.ts";
5
5
  import type { RemoteMcpClientFactory } from "./mcp.ts";
6
6
  import { createRouterRuntime } from "./runtime.ts";
@@ -23,7 +23,7 @@ describe("router execution", () => {
23
23
  exports: ["*"],
24
24
  },
25
25
  },
26
- executors: {
26
+ modelNamespaces: {
27
27
  default: {
28
28
  exports: ["*"],
29
29
  models: {
@@ -132,7 +132,7 @@ describe("router execution", () => {
132
132
  }],
133
133
  },
134
134
  },
135
- executors: {
135
+ modelNamespaces: {
136
136
  default: {
137
137
  exports: ["*"],
138
138
  models: {
@@ -239,7 +239,7 @@ describe("router execution", () => {
239
239
  exports: ["*"],
240
240
  },
241
241
  },
242
- executors: {
242
+ modelNamespaces: {
243
243
  default: {
244
244
  exports: ["*"],
245
245
  models: {
@@ -312,7 +312,7 @@ describe("router execution", () => {
312
312
  exports: ["*"],
313
313
  },
314
314
  },
315
- executors: {
315
+ modelNamespaces: {
316
316
  default: {
317
317
  exports: ["*"],
318
318
  models: {
@@ -387,7 +387,7 @@ describe("router execution", () => {
387
387
  exports: ["*"],
388
388
  },
389
389
  },
390
- executors: {
390
+ modelNamespaces: {
391
391
  default: {
392
392
  exports: ["*"],
393
393
  models: {
@@ -451,11 +451,11 @@ describe("router execution", () => {
451
451
  const runtime = createRouterRuntime({
452
452
  config: {
453
453
  providers: {},
454
- executors: {
454
+ modelNamespaces: {
455
455
  enei: {
456
456
  exports: ["enei-*"],
457
457
  models: {
458
- "enei-1": { executor: "enei-1" },
458
+ "enei-1": { executorId: "enei-1" },
459
459
  },
460
460
  },
461
461
  },
@@ -479,7 +479,7 @@ describe("router execution", () => {
479
479
  const response = await handleChatCompletions({
480
480
  runtime,
481
481
  ctx,
482
- executors: {
482
+ executorImplementations: {
483
483
  "enei-1": {
484
484
  async execute(request) {
485
485
  return appendAssistantMessage(request, "done");
@@ -515,7 +515,7 @@ describe("router execution", () => {
515
515
  exports: ["*"],
516
516
  },
517
517
  },
518
- executors: {
518
+ modelNamespaces: {
519
519
  default: {
520
520
  exports: ["*"],
521
521
  models: {
@@ -589,7 +589,7 @@ describe("router execution", () => {
589
589
  exports: ["*"],
590
590
  },
591
591
  },
592
- executors: {
592
+ modelNamespaces: {
593
593
  default: {
594
594
  exports: ["*"],
595
595
  models: {