@neutrome/open-ai-router 0.3.7 → 0.4.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@neutrome/open-ai-router",
3
- "version": "0.3.7",
3
+ "version": "0.4.0",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": "./src/index.ts"
@@ -8,8 +8,8 @@
8
8
  "dependencies": {
9
9
  "@modelcontextprotocol/sdk": "^1.29.0",
10
10
  "hono": "^4.12.18",
11
- "@neutrome/lil-engine": "0.2.0",
12
- "@neutrome/lilsdk-ts": "0.2.2"
11
+ "@neutrome/lil-engine": "0.2.1",
12
+ "@neutrome/lilsdk-ts": "0.2.3"
13
13
  },
14
14
  "devDependencies": {
15
15
  "@types/node": "^25.9.3",
@@ -31,4 +31,20 @@ describe("createTraceCollector", () => {
31
31
  ';request:0\nMSG_START\n\n;provider-request:1\nSET_MODEL "gpt-4o"\n\n;provider-response-chunk:2\nSTREAM_DELTA "Hi"',
32
32
  );
33
33
  });
34
+
35
+ it("appends structured timing comments with durationMs", () => {
36
+ const collector = createTraceCollector("00000000-0000-4000-8000-000000000001");
37
+
38
+ collector.observe({
39
+ kind: "provider.finished",
40
+ requestId: "request-1",
41
+ executionId: "provider-1",
42
+ timestamp: "2026-06-24T12:00:03.000Z",
43
+ data: { durationMs: 123 },
44
+ });
45
+
46
+ expect(collector.getTrace().rawTrace).toBe(
47
+ ';timing {"kind":"provider","event":"provider.finished","executionId":"provider-1","durationMs":123}',
48
+ );
49
+ });
34
50
  });
package/src/observer.ts CHANGED
@@ -53,6 +53,7 @@ export function createTraceCollector(spanId = crypto.randomUUID()): {
53
53
  return {
54
54
  observe(event) {
55
55
  appendLilSegment(event);
56
+ appendTimingComment(event);
56
57
 
57
58
  switch (event.kind) {
58
59
  case "request.received":
@@ -156,6 +157,26 @@ export function createTraceCollector(spanId = crypto.randomUUID()): {
156
157
  if (!lilText) return;
157
158
  rawSegments.push(`;${traceSegmentLabel(event)}:${rawSegmentIndex++}\n${lilText}`);
158
159
  }
160
+
161
+ function appendTimingComment(event: AuditEvent): void {
162
+ const rawDurationMs = event.data?.durationMs;
163
+ if (typeof rawDurationMs !== "number" || !Number.isFinite(rawDurationMs) || rawDurationMs < 0) return;
164
+ const durationMs = Math.round(rawDurationMs);
165
+ rawSegments.push(`;timing ${JSON.stringify({
166
+ kind: traceTimingKind(event),
167
+ event: event.kind,
168
+ executionId: event.executionId,
169
+ durationMs,
170
+ })}`);
171
+ }
172
+ }
173
+
174
+ function traceTimingKind(event: AuditEvent): TraceSpanKind | "remote_mcp" {
175
+ if (event.kind.startsWith("provider.")) return "provider";
176
+ if (event.kind.startsWith("executor.")) return "executor";
177
+ if (event.kind.startsWith("remote_mcp.")) return "remote_mcp";
178
+ if (event.kind.startsWith("tool.")) return "tool";
179
+ return "request";
159
180
  }
160
181
 
161
182
  function traceSegmentLabel(event: AuditEvent): string {
@@ -96,6 +96,7 @@ describe("router execution", () => {
96
96
 
97
97
  it("injects and executes attached remote mcp tools", async () => {
98
98
  const capturedBodies: Record<string, unknown>[] = [];
99
+ const events: Array<{ kind: string; data?: Record<string, unknown> }> = [];
99
100
  const close = vi.fn(async () => {});
100
101
  const callTool = vi.fn(async (name: string, args: Record<string, unknown>) => {
101
102
  expect(name).toBe("lookup");
@@ -214,13 +215,23 @@ describe("router execution", () => {
214
215
  env: {},
215
216
  };
216
217
 
217
- const response = await handleChatCompletions({ runtime, ctx, remoteMcpClientFactory });
218
+ const response = await handleChatCompletions({
219
+ runtime,
220
+ ctx,
221
+ remoteMcpClientFactory,
222
+ observe(event) {
223
+ events.push(event);
224
+ },
225
+ });
218
226
 
219
227
  expect(response.status).toBe(200);
220
228
  expect(remoteMcpClientFactory).toHaveBeenCalledOnce();
221
229
  expect(callTool).toHaveBeenCalledOnce();
222
230
  expect(close).toHaveBeenCalledOnce();
223
231
  expect(capturedBodies).toHaveLength(2);
232
+ expect(events.find((event) => event.kind === "remote_mcp.connect")?.data?.durationMs).toEqual(expect.any(Number));
233
+ expect(events.find((event) => event.kind === "remote_mcp.tool_call")?.data?.durationMs).toEqual(expect.any(Number));
234
+ expect(events.find((event) => event.kind === "tool.executed")?.data?.durationMs).toEqual(expect.any(Number));
224
235
  expect(await response.json()).toMatchObject({
225
236
  choices: [{ message: { content: "done" } }],
226
237
  });
@@ -638,4 +649,78 @@ describe("router execution", () => {
638
649
  },
639
650
  });
640
651
  });
652
+
653
+ it("observes non-2xx provider errors as LIL and emits the requested response style", async () => {
654
+ const events: Array<{ kind: string; data?: Record<string, unknown> }> = [];
655
+ const runtime = createRouterRuntime({
656
+ config: {
657
+ providers: {
658
+ anthropic: {
659
+ api_base_url: "https://api.anthropic.com",
660
+ style: "anthropic-messages",
661
+ exports: ["*"],
662
+ },
663
+ },
664
+ modelNamespaces: {
665
+ default: {
666
+ exports: ["*"],
667
+ models: {
668
+ "claude-sonnet-4": { provider: "anthropic", model: "claude-sonnet-4" },
669
+ },
670
+ },
671
+ },
672
+ },
673
+ fetchImpl: vi.fn(async () =>
674
+ new Response(
675
+ JSON.stringify({
676
+ type: "error",
677
+ error: { type: "overloaded_error", message: "Provider is overloaded" },
678
+ request_id: "req_upstream",
679
+ }),
680
+ {
681
+ status: 529,
682
+ headers: { "content-type": "application/json; charset=utf-8" },
683
+ },
684
+ ),
685
+ ),
686
+ });
687
+
688
+ const ctx: RequestContext = {
689
+ request: new Request("http://local/v1/chat/completions", {
690
+ method: "POST",
691
+ headers: { "content-type": "application/json" },
692
+ body: JSON.stringify({
693
+ model: "claude-sonnet-4",
694
+ messages: [{ role: "user", content: "hi" }],
695
+ }),
696
+ }),
697
+ incomingAuth: new Map(),
698
+ env: {},
699
+ };
700
+
701
+ const response = await handleChatCompletions({
702
+ runtime,
703
+ ctx,
704
+ observe(event) {
705
+ events.push(event);
706
+ },
707
+ });
708
+
709
+ expect(response.status).toBe(529);
710
+ expect(await response.json()).toEqual({
711
+ error: {
712
+ message: "Provider is overloaded",
713
+ type: "overloaded_error",
714
+ param: null,
715
+ code: null,
716
+ },
717
+ });
718
+ const observedError = events.find((event) =>
719
+ event.kind === "provider.response" &&
720
+ typeof event.data?.lilText === "string" &&
721
+ event.data.lilText.includes("ERR_START"),
722
+ );
723
+ expect(observedError?.data?.lilText).toContain('ERR_MESSAGE "Provider is overloaded"');
724
+ expect(observedError?.data?.lilText).toContain('ERR_DATA "request_id"="req_upstream"');
725
+ });
641
726
  });
@@ -1,11 +1,16 @@
1
1
  import {
2
+ appendErrorBlock,
3
+ createProgram,
2
4
  emitChatCompletionsResponse,
5
+ emitProviderError,
3
6
  emitProviderRequest,
4
7
  emitProviderResponse,
5
8
  emitProviderStreamChunk,
9
+ firstError,
6
10
  getModel,
7
11
  isStreaming,
8
12
  parseChatCompletionsRequest,
13
+ parseProviderError,
9
14
  parseProviderRequest,
10
15
  parseProviderResponse,
11
16
  parseProviderStreamChunk,
@@ -95,8 +100,7 @@ export class ProviderHttpError extends Error {
95
100
  constructor(
96
101
  message: string,
97
102
  readonly status: number,
98
- readonly body: Uint8Array,
99
- readonly contentType: string,
103
+ readonly program: Program,
100
104
  ) {
101
105
  super(message);
102
106
  }
@@ -214,7 +218,7 @@ export async function handleChatCompletions(
214
218
  await remoteMcp.close();
215
219
  }
216
220
  } catch (error) {
217
- return errorResponse(error);
221
+ return errorResponse(error, "chat-completions");
218
222
  }
219
223
  }
220
224
 
@@ -291,7 +295,7 @@ async function handleProviderStyle(
291
295
  await remoteMcp.close();
292
296
  }
293
297
  } catch (error) {
294
- return errorResponse(error);
298
+ return errorResponse(error, style);
295
299
  }
296
300
  }
297
301
 
@@ -357,7 +361,15 @@ export function createFetchProviderInvoker(
357
361
  timed.signal,
358
362
  );
359
363
  if (!upstream.ok) {
360
- throw await toProviderError(provider.name, upstream);
364
+ const providerError = await toProviderError(provider, upstream);
365
+ emitObservedProgram(providerCtx.observe, "provider.response", providerError.program, {
366
+ requestId: providerCtx.requestId,
367
+ executionId: providerCtx.executionId,
368
+ ...(providerCtx.parentExecutionId ? { parentExecutionId: providerCtx.parentExecutionId } : {}),
369
+ provider: provider.name,
370
+ model: providerCtx.target.model,
371
+ });
372
+ throw providerError;
361
373
  }
362
374
 
363
375
  const bytes = new Uint8Array(await upstream.arrayBuffer());
@@ -406,7 +418,15 @@ export function createFetchProviderInvoker(
406
418
  timed.signal,
407
419
  );
408
420
  if (!upstream.ok) {
409
- throw await toProviderError(provider.name, upstream);
421
+ const providerError = await toProviderError(provider, upstream);
422
+ emitObservedProgram(providerCtx.observe, "provider.response", providerError.program, {
423
+ requestId: providerCtx.requestId,
424
+ executionId: providerCtx.executionId,
425
+ ...(providerCtx.parentExecutionId ? { parentExecutionId: providerCtx.parentExecutionId } : {}),
426
+ provider: provider.name,
427
+ model: providerCtx.target.model,
428
+ });
429
+ throw providerError;
410
430
  }
411
431
  if (!isSse(upstream.headers) || !upstream.body) {
412
432
  throw new RouterError(
@@ -537,11 +557,9 @@ async function streamExecutionResponse(
537
557
  } catch (error) {
538
558
  done = true;
539
559
  await cleanup?.();
540
- controller.enqueue(
541
- encoder.encode(
542
- `event: error\ndata: ${JSON.stringify({ message: error instanceof Error ? error.message : String(error) })}\n\n`,
543
- ),
544
- );
560
+ const program = errorProgramFromUnknown(error);
561
+ const bytes = emitProviderError(responseStyle, program);
562
+ controller.enqueue(encoder.encode(`event: error\ndata: ${decoder.decode(bytes)}\n\n`));
545
563
  controller.close();
546
564
  }
547
565
  },
@@ -689,74 +707,103 @@ function getProviderOrThrow(runtime: RouterRuntime, providerName: string | undef
689
707
  }
690
708
 
691
709
  async function toProviderError(
692
- providerName: string,
710
+ provider: ProviderRuntime,
693
711
  response: Response,
694
712
  ): Promise<ProviderHttpError> {
695
713
  const body = new Uint8Array(await response.arrayBuffer());
714
+ const program = parseProviderError(provider.style, body, {
715
+ provider: provider.name,
716
+ status: response.status,
717
+ contentType: response.headers.get("content-type"),
718
+ });
719
+ const summary = firstError(program)?.message || `Provider ${provider.name} returned ${response.status}`;
696
720
  return new ProviderHttpError(
697
- `Provider ${providerName} returned ${response.status}`,
721
+ summary,
698
722
  response.status,
699
- body,
700
- response.headers.get("content-type") ?? "application/json; charset=utf-8",
723
+ program,
701
724
  );
702
725
  }
703
726
 
704
- function errorResponse(error: unknown): Response {
727
+ function errorResponse(error: unknown, responseStyle: ProviderStyle): Response {
705
728
  if (error instanceof ProviderHttpError) {
706
- return new Response(toBody(error.body), {
729
+ return new Response(toBody(emitProviderError(responseStyle, error.program)), {
707
730
  status: error.status,
708
731
  headers: {
709
- "content-type": error.contentType,
732
+ "content-type": "application/json; charset=utf-8",
710
733
  },
711
734
  });
712
735
  }
713
736
 
714
737
  if (error instanceof RouterError) {
715
- return new Response(
716
- JSON.stringify({
717
- error: {
718
- message: error.message,
719
- type: error.type,
720
- param: null,
721
- code: error.code,
722
- },
723
- }),
724
- {
725
- status: error.status,
726
- headers: { "content-type": "application/json; charset=utf-8" },
727
- },
728
- );
738
+ return emitErrorProgramResponse(routerErrorProgram(error), error.status, responseStyle);
729
739
  }
730
740
 
731
741
  if (error instanceof ExecutionError) {
732
742
  const providerError = unwrapCause(error.cause, ProviderHttpError);
733
743
  if (providerError) {
734
- return errorResponse(providerError);
744
+ return errorResponse(providerError, responseStyle);
735
745
  }
736
746
  const nestedRouterError = unwrapCause(error.cause, RouterError);
737
747
  if (nestedRouterError) {
738
- return errorResponse(nestedRouterError);
748
+ return errorResponse(nestedRouterError, responseStyle);
739
749
  }
740
- return errorResponse(routerErrorFromExecutionError(error));
750
+ const routerError = routerErrorFromExecutionError(error);
751
+ return emitErrorProgramResponse(routerErrorProgram(routerError), routerError.status, responseStyle);
741
752
  }
742
753
 
743
754
  console.error("[open-ai-router] unhandled error:", error);
744
- return new Response(
745
- JSON.stringify({
746
- error: {
747
- message: "Internal router error",
748
- type: "invalid_request_error",
749
- param: null,
750
- code: "internal_error",
751
- },
752
- }),
753
- {
755
+ return emitErrorProgramResponse(
756
+ appendErrorBlock(createProgram(), {
757
+ source: "router.internal",
758
+ kind: "invalid_request_error",
754
759
  status: 500,
755
- headers: { "content-type": "application/json; charset=utf-8" },
756
- },
760
+ message: "Internal router error",
761
+ data: { code: "internal_error", param: null },
762
+ }),
763
+ 500,
764
+ responseStyle,
757
765
  );
758
766
  }
759
767
 
768
+ function emitErrorProgramResponse(program: Program, status: number, responseStyle: ProviderStyle): Response {
769
+ return new Response(toBody(emitProviderError(responseStyle, program)), {
770
+ status,
771
+ headers: { "content-type": "application/json; charset=utf-8" },
772
+ });
773
+ }
774
+
775
+ function routerErrorProgram(error: RouterError): Program {
776
+ return appendErrorBlock(createProgram(), {
777
+ source: "router",
778
+ kind: error.type,
779
+ status: error.status,
780
+ message: error.message,
781
+ data: { code: error.code, param: null },
782
+ });
783
+ }
784
+
785
+ function errorProgramFromUnknown(error: unknown): Program {
786
+ if (error instanceof ProviderHttpError) {
787
+ return error.program;
788
+ }
789
+ if (error instanceof RouterError) {
790
+ return routerErrorProgram(error);
791
+ }
792
+ if (error instanceof ExecutionError) {
793
+ const providerError = unwrapCause(error.cause, ProviderHttpError);
794
+ if (providerError) return providerError.program;
795
+ const nestedRouterError = unwrapCause(error.cause, RouterError);
796
+ if (nestedRouterError) return routerErrorProgram(nestedRouterError);
797
+ return routerErrorProgram(routerErrorFromExecutionError(error));
798
+ }
799
+ return appendErrorBlock(createProgram(), {
800
+ source: "router.stream",
801
+ kind: "stream_error",
802
+ status: 500,
803
+ message: error instanceof Error ? error.message : String(error),
804
+ });
805
+ }
806
+
760
807
  function routerErrorFromExecutionError(error: ExecutionError): RouterError {
761
808
  switch (error.kind) {
762
809
  case "validation":
@@ -76,6 +76,7 @@ export function createExecutionRuntime(options: ExecutionRuntimeOptions = {}): E
76
76
 
77
77
  if (target.kind === "executor") {
78
78
  const executor = await getExecutorOrThrow(target.executorId);
79
+ const startedMs = Date.now();
79
80
  observe(createAuditEvent({
80
81
  kind: "executor.started",
81
82
  requestId,
@@ -111,6 +112,7 @@ export function createExecutionRuntime(options: ExecutionRuntimeOptions = {}): E
111
112
  requestId,
112
113
  executionId,
113
114
  target: executionTargetAuditRef(target),
115
+ data: { durationMs: Date.now() - startedMs },
114
116
  ...withParentExecutionId(parentExecutionId),
115
117
  }));
116
118
  return result;
@@ -122,6 +124,7 @@ export function createExecutionRuntime(options: ExecutionRuntimeOptions = {}): E
122
124
  }
123
125
 
124
126
  const providerInvoker = getProviderInvokerOrThrow();
127
+ const startedMs = Date.now();
125
128
  const providerCtx = createProviderInvocationContext(
126
129
  requestId,
127
130
  executionId,
@@ -162,6 +165,7 @@ export function createExecutionRuntime(options: ExecutionRuntimeOptions = {}): E
162
165
  requestId,
163
166
  executionId,
164
167
  target: executionTargetAuditRef(target),
168
+ data: { durationMs: Date.now() - startedMs },
165
169
  ...withParentExecutionId(parentExecutionId),
166
170
  }));
167
171
  return result;
@@ -208,6 +212,7 @@ export function createExecutionRuntime(options: ExecutionRuntimeOptions = {}): E
208
212
 
209
213
  if (target.kind === "executor") {
210
214
  const executor = await getExecutorOrThrow(target.executorId);
215
+ const startedMs = Date.now();
211
216
  observe(createAuditEvent({
212
217
  kind: "executor.stream_started",
213
218
  requestId,
@@ -245,6 +250,7 @@ export function createExecutionRuntime(options: ExecutionRuntimeOptions = {}): E
245
250
  requestId,
246
251
  executionId,
247
252
  target: executionTargetAuditRef(target),
253
+ data: { durationMs: Date.now() - startedMs },
248
254
  ...withParentExecutionId(parentExecutionId),
249
255
  }));
250
256
  return;
@@ -256,6 +262,7 @@ export function createExecutionRuntime(options: ExecutionRuntimeOptions = {}): E
256
262
  }
257
263
 
258
264
  const providerInvoker = getProviderInvokerOrThrow();
265
+ const startedMs = Date.now();
259
266
  const providerCtx = createProviderInvocationContext(
260
267
  requestId,
261
268
  executionId,
@@ -298,6 +305,7 @@ export function createExecutionRuntime(options: ExecutionRuntimeOptions = {}): E
298
305
  requestId,
299
306
  executionId,
300
307
  target: executionTargetAuditRef(target),
308
+ data: { durationMs: Date.now() - startedMs },
301
309
  ...withParentExecutionId(parentExecutionId),
302
310
  }));
303
311
  } catch (error) {
package/src/router/mcp.ts CHANGED
@@ -45,8 +45,35 @@ export function createRemoteMcpToolSet(
45
45
  description: snapshot.description || `Remote MCP tool ${server.name}/${snapshot.name}`,
46
46
  schema: normalizeSchema(snapshot.inputSchema),
47
47
  async execute(args: Record<string, unknown>, ctx: ExecutorContext) {
48
+ const connectStartedMs = Date.now();
48
49
  const client = await pool.get(server, ctx.signal);
50
+ ctx.observe({
51
+ kind: "remote_mcp.connect",
52
+ requestId: ctx.requestId,
53
+ executionId: `remote_mcp_connect_${server.name}_${crypto.randomUUID()}`,
54
+ parentExecutionId: ctx.executionId,
55
+ timestamp: new Date().toISOString(),
56
+ data: {
57
+ server: server.name,
58
+ toolName: name,
59
+ durationMs: Date.now() - connectStartedMs,
60
+ },
61
+ });
62
+ const callStartedMs = Date.now();
49
63
  const result = await client.callTool(snapshot.name, args);
64
+ ctx.observe({
65
+ kind: "remote_mcp.tool_call",
66
+ requestId: ctx.requestId,
67
+ executionId: `remote_mcp_tool_${server.name}_${snapshot.name}_${crypto.randomUUID()}`,
68
+ parentExecutionId: ctx.executionId,
69
+ timestamp: new Date().toISOString(),
70
+ data: {
71
+ server: server.name,
72
+ toolName: name,
73
+ remoteToolName: snapshot.name,
74
+ durationMs: Date.now() - callStartedMs,
75
+ },
76
+ });
50
77
  return stringifyMcpToolResult(result);
51
78
  },
52
79
  });
@@ -1,61 +0,0 @@
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;