omnius 1.0.591 → 1.0.592

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.
Files changed (131) hide show
  1. package/.aiwg/addons/omnius-docs/README.md +15 -1
  2. package/.aiwg/addons/omnius-docs/manifest.json +28 -68
  3. package/.aiwg/addons/omnius-docs/skills/agent-failure-recovery/SKILL.md +2 -1
  4. package/.aiwg/addons/omnius-docs/skills/browser-interaction-validation/SKILL.md +2 -1
  5. package/.aiwg/addons/omnius-docs/skills/evidence-directed-delivery/SKILL.md +2 -1
  6. package/.aiwg/addons/omnius-docs/skills/hardware-evidence-audit/SKILL.md +2 -1
  7. package/.aiwg/addons/omnius-docs/skills/omnius-docs/SKILL.md +17 -7
  8. package/.aiwg/addons/omnius-docs/skills/omnius-inference-docs/SKILL.md +27 -0
  9. package/.aiwg/addons/omnius-docs/skills/omnius-integration-docs/SKILL.md +21 -0
  10. package/.aiwg/addons/omnius-docs/skills/omnius-ops-docs/SKILL.md +2 -0
  11. package/.aiwg/addons/omnius-docs/skills/omnius-realtime-docs/SKILL.md +2 -0
  12. package/.aiwg/addons/omnius-docs/skills/omnius-sponsor-docs/SKILL.md +2 -0
  13. package/.aiwg/addons/omnius-docs/skills/omnius-telegram-docs/SKILL.md +2 -0
  14. package/.aiwg/addons/omnius-docs/skills/omnius-tools-docs/SKILL.md +23 -0
  15. package/.aiwg/addons/omnius-docs/skills/omnius-version-compatibility-docs/SKILL.md +23 -0
  16. package/.aiwg/addons/omnius-docs/skills/runtime-provenance-audit/SKILL.md +2 -1
  17. package/.aiwg/addons/omnius-docs/skills/secrets-and-config-audit/SKILL.md +2 -1
  18. package/.aiwg/addons/omnius-docs/skills/test-surface-audit/SKILL.md +2 -1
  19. package/.aiwg/addons/omnius-docs/skills/workspace-reality-audit/SKILL.md +2 -1
  20. package/.aiwg/addons/omnius-rest-docs/README.md +3 -0
  21. package/.aiwg/addons/omnius-rest-docs/manifest.json +27 -20
  22. package/.aiwg/addons/omnius-rest-docs/skills/omnius-rest-docs/SKILL.md +9 -5
  23. package/README.md +36 -0
  24. package/dist/discovery.d.ts +50 -0
  25. package/dist/index.js +5975 -4021
  26. package/dist/library.d.ts +7 -0
  27. package/dist/library.js +950 -0
  28. package/dist/postinstall-daemon.cjs +18 -0
  29. package/dist/providerRegistry.d.ts +80 -0
  30. package/dist/service-version.d.ts +35 -0
  31. package/docs/.vitepress/config.mts +8 -0
  32. package/docs/DISCOVERY.json +20224 -0
  33. package/docs/DISCOVERY.md +648 -0
  34. package/docs/HANDOFF-crl-encoder-decoder-fix.md +129 -0
  35. package/docs/agent-memory/INDEX.md +9 -4
  36. package/docs/agent-memory/index.md +7 -0
  37. package/docs/concept-relational-language.md +869 -0
  38. package/docs/context-management-medium-models-proposal.md +449 -0
  39. package/docs/dedup-false-positive-meta-analysis.md +96 -0
  40. package/docs/discovery/catalog-overrides.json +724 -0
  41. package/docs/duplicate-calls-root-cause-analysis.md +91 -0
  42. package/docs/duplicate-calls-root-cause-deep.md +155 -0
  43. package/docs/ephemeral-skill-pack-small-context.md +57 -0
  44. package/docs/explorations/context-window-todo-association.md +156 -0
  45. package/docs/explorations/todo-association-verify.json +30 -0
  46. package/docs/explorations/verification-ledger.json +45 -0
  47. package/docs/explorations/verify-todo-association.sh +30 -0
  48. package/docs/flowstate.md +806 -0
  49. package/docs/getting-started/install.md +24 -0
  50. package/docs/getting-started/model-providers.md +13 -0
  51. package/docs/guides/agent-integration.md +87 -0
  52. package/docs/guides/bring-your-own-inference.md +126 -0
  53. package/docs/guides/tools-and-web-search.md +95 -0
  54. package/docs/index.md +14 -0
  55. package/docs/longhaul-35b-workorders.md +496 -0
  56. package/docs/memory-integration-analysis.md +303 -0
  57. package/docs/model-capability-awareness-and-multimodal-memory-root-fix.md +799 -0
  58. package/docs/multimodal-identity-memory-implementation.md +76 -0
  59. package/docs/omnius-self-edit-eval-2026-06-10.md +169 -0
  60. package/docs/opencode-agentic-loop-comparison.md +290 -0
  61. package/docs/operations/security-and-remote-access.md +2 -2
  62. package/docs/operations/version-compatibility.md +63 -0
  63. package/docs/proposals/git-progress-tracking-strategy.md +289 -0
  64. package/docs/proposals/opencode-modules/backendAdapter.ts +443 -0
  65. package/docs/proposals/opencode-modules/childSession.ts +288 -0
  66. package/docs/proposals/opencode-modules/compactionAgent.ts +101 -0
  67. package/docs/proposals/opencode-modules/orchestrator.ts +387 -0
  68. package/docs/proposals/opencode-modules/runner.ts +258 -0
  69. package/docs/reference/auth-map.md +87 -196
  70. package/docs/reference/configuration.md +27 -0
  71. package/docs/reference/rest-api.md +7 -0
  72. package/docs/reference/slash-commands.md +125 -2
  73. package/docs/research/_archived/README.md +18 -0
  74. package/docs/research/_archived/context_window_attention_model.py +418 -0
  75. package/docs/research/_archived/context_window_attention_spec.md +55 -0
  76. package/docs/research/_archived/context_window_attention_weights.json +68 -0
  77. package/docs/research/k-splanifolds.pdf +0 -0
  78. package/docs/research/personality-verbosity-control.md +293 -0
  79. package/docs/rest/INDEX.md +7 -0
  80. package/docs/rest/QUICKREF.md +18 -0
  81. package/docs/rest/REST-DOCS-MANIFEST.json +1 -0
  82. package/docs/rest/auth-and-scopes.md +7 -1
  83. package/docs/rest/endpoints/discovery.md +44 -0
  84. package/docs/rest/endpoints/events.md +5 -0
  85. package/docs/rest/endpoints/tools.md +9 -0
  86. package/docs/reviews/adversary-system-review.md +42 -0
  87. package/docs/sana-and-video-generation-integration-plan.md +712 -0
  88. package/docs/session-diary-llm-training-analysis.md +218 -0
  89. package/docs/telegram-dmn-curiosity-outreach-scaffold.md +91 -0
  90. package/docs/telegram-mid-horizon-download-loop-handoff.md +468 -0
  91. package/docs/telegram-reflection-corpus-integration-plan.md +306 -0
  92. package/docs/telegram-unified-tooling-architecture.md +332 -0
  93. package/docs/threat-model.md +868 -0
  94. package/docs/trajectory-grounding.md +160 -0
  95. package/docs/voice-flow-architecture.md +489 -0
  96. package/docs/work-orders/WO-AM-GAPS.md +638 -0
  97. package/docs/work-orders/daemon-hud-ui-overhaul.md +82 -0
  98. package/docs/work-orders/hermes-architecture-deltas/01-public-scrutiny-provenance-control/INDEX.md +21 -0
  99. package/docs/work-orders/hermes-architecture-deltas/01-public-scrutiny-provenance-control/WORKORDER.md +225 -0
  100. package/docs/work-orders/hermes-architecture-deltas/02-context-engine-plugin-boundary/INDEX.md +20 -0
  101. package/docs/work-orders/hermes-architecture-deltas/02-context-engine-plugin-boundary/WORKORDER.md +198 -0
  102. package/docs/work-orders/hermes-architecture-deltas/03-typed-gateway-event-stream/INDEX.md +19 -0
  103. package/docs/work-orders/hermes-architecture-deltas/03-typed-gateway-event-stream/WORKORDER.md +172 -0
  104. package/docs/work-orders/hermes-architecture-deltas/04-task-local-gateway-context/INDEX.md +19 -0
  105. package/docs/work-orders/hermes-architecture-deltas/04-task-local-gateway-context/WORKORDER.md +169 -0
  106. package/docs/work-orders/hermes-architecture-deltas/05-process-lifecycle-monitoring-notifications/INDEX.md +22 -0
  107. package/docs/work-orders/hermes-architecture-deltas/05-process-lifecycle-monitoring-notifications/WORKORDER.md +189 -0
  108. package/docs/work-orders/hermes-architecture-deltas/06-vision-evidence-routing-ladder/INDEX.md +22 -0
  109. package/docs/work-orders/hermes-architecture-deltas/06-vision-evidence-routing-ladder/WORKORDER.md +199 -0
  110. package/docs/work-orders/hermes-architecture-deltas/07-durable-multi-agent-kanban/INDEX.md +20 -0
  111. package/docs/work-orders/hermes-architecture-deltas/07-durable-multi-agent-kanban/WORKORDER.md +174 -0
  112. package/docs/work-orders/hermes-architecture-deltas/08-completion-critic-reconciliation-ledger/INDEX.md +22 -0
  113. package/docs/work-orders/hermes-architecture-deltas/08-completion-critic-reconciliation-ledger/WORKORDER.md +226 -0
  114. package/docs/work-orders/hermes-architecture-deltas/INDEX.md +38 -0
  115. package/docs/work-orders/omnius-context-engineering-behavior-fixes.md +281 -0
  116. package/docs/work-orders/telegram-dropbear-context-rca-workorder.md +202 -0
  117. package/docs/work-orders/world-class-memory-compiler/README.md +162 -0
  118. package/docs/work-orders/world-class-memory-compiler/TRACKER.md +179 -0
  119. package/docs/work-orders/world-class-memory-compiler/WO-01-exact-request-budget.md +79 -0
  120. package/docs/work-orders/world-class-memory-compiler/WO-02-typed-memory-fabric.md +65 -0
  121. package/docs/work-orders/world-class-memory-compiler/WO-03-dependency-working-set.md +55 -0
  122. package/docs/work-orders/world-class-memory-compiler/WO-04-inference-memory-compiler.md +67 -0
  123. package/docs/work-orders/world-class-memory-compiler/WO-05-artifact-fidelity-materialization.md +72 -0
  124. package/docs/work-orders/world-class-memory-compiler/WO-06-temporal-hybrid-retrieval.md +49 -0
  125. package/docs/work-orders/world-class-memory-compiler/WO-07-evaluation-harness.md +45 -0
  126. package/docs/work-orders/world-class-memory-compiler/WO-08-rollout-legacy-removal.md +45 -0
  127. package/docs/x402-remote-inference-plan.md +323 -0
  128. package/npm-shrinkwrap.json +108 -117
  129. package/package.json +7 -6
  130. package/templates/AGENTS.md +6 -0
  131. package/templates/OMNIUS.md +20 -0
@@ -0,0 +1,443 @@
1
+ /**
2
+ * backendAdapter.ts — Unified LLM backend adapter interface.
3
+ *
4
+ * Normalizes differences between backends (Ollama, vLLM, OpenAI, etc.) into
5
+ * a common LLMEvent stream type. Each backend implements the LLMBackend interface
6
+ * and produces LLMEvent objects that the runner consumes uniformly.
7
+ */
8
+
9
+ // ─── LLMEvent stream type ───────────────────────────────────────────────────
10
+
11
+ export interface LLMEvent {
12
+ type: 'chunk' | 'tool-call' | 'tool-result' | 'finish' | 'error';
13
+ content?: string;
14
+ toolCall?: { id: string; name: string; args: string };
15
+ toolResult?: { id: string; content: string; error?: string };
16
+ finishReason?: string | null;
17
+ error?: string;
18
+ }
19
+
20
+ export type LLMEventStream = AsyncIterable<LLMEvent>;
21
+
22
+ // ─── Backend adapter interface ──────────────────────────────────────────────
23
+
24
+ export interface LLMBackend {
25
+ /** Unique identifier for this backend */
26
+ readonly id: string;
27
+
28
+ /** Human-readable name */
29
+ readonly name: string;
30
+
31
+ /**
32
+ * Send a prompt (with optional tool definitions) and receive a normalized
33
+ * event stream. The caller iterates the stream to consume chunks, tool calls,
34
+ * and the final finish event.
35
+ */
36
+ complete(
37
+ messages: Array<{ role: 'system' | 'user' | 'assistant'; content: string }>,
38
+ tools?: Array<{ name: string; description?: string; parameters: Record<string, unknown> }>,
39
+ options?: BackendOptions,
40
+ ): LLMEventStream;
41
+
42
+ /** Check if the backend is healthy / reachable */
43
+ healthCheck(): Promise<boolean>;
44
+ }
45
+
46
+ export interface BackendOptions {
47
+ temperature?: number;
48
+ maxTokens?: number;
49
+ topP?: number;
50
+ stopSequences?: string[];
51
+ [key: string]: unknown;
52
+ }
53
+
54
+ // ─── Backend registry ───────────────────────────────────────────────────────
55
+
56
+ const registeredBackends = new Map<string, LLMBackend>();
57
+
58
+ export function registerBackend(backend: LLMBackend): void {
59
+ registeredBackends.set(backend.id, backend);
60
+ }
61
+
62
+ export function getBackend(id: string): LLMBackend | undefined {
63
+ return registeredBackends.get(id);
64
+ }
65
+
66
+ export function listBackends(): LLMBackend[] {
67
+ return Array.from(registeredBackends.values());
68
+ }
69
+
70
+ // ─── Cascade backend (fallback chain) ───────────────────────────────────────
71
+
72
+ export class CascadeBackend implements LLMBackend {
73
+ readonly id = 'cascade';
74
+ readonly name = 'Cascade (fallback chain)';
75
+
76
+ constructor(private readonly backends: LLMBackend[]) {}
77
+
78
+ async* complete(
79
+ messages: Array<{ role: 'system' | 'user' | 'assistant'; content: string }>,
80
+ tools?: Array<{ name: string; description?: string; parameters: Record<string, unknown> }>,
81
+ options?: BackendOptions,
82
+ ): AsyncIterable<LLMEvent> {
83
+ let lastError: Error | undefined;
84
+
85
+ for (const backend of this.backends) {
86
+ try {
87
+ const stream = await backend.complete(messages, tools, options);
88
+ // Forward events from this backend
89
+ for await (const event of stream) {
90
+ yield event;
91
+ }
92
+ return; // Success — stop trying
93
+ } catch (err) {
94
+ lastError = err instanceof Error ? err : new Error(String(err));
95
+ continue; // Try next backend
96
+ }
97
+ }
98
+
99
+ // All backends failed — yield an error event
100
+ yield {
101
+ type: 'error',
102
+ error: `All ${this.backends.length} backends failed. Last error: ${lastError?.message}`,
103
+ };
104
+ }
105
+
106
+ async healthCheck(): Promise<boolean> {
107
+ return this.backends.some((b) => b.healthCheck());
108
+ }
109
+ }
110
+
111
+ // ─── Ollama backend adapter ─────────────────────────────────────────────────
112
+
113
+ export class OllamaBackend implements LLMBackend {
114
+ readonly id = 'ollama';
115
+ readonly name = 'Ollama';
116
+
117
+ constructor(
118
+ private readonly model: string,
119
+ private readonly baseUrl: string = 'http://localhost:11434',
120
+ ) {}
121
+
122
+ async *complete(
123
+ messages: Array<{ role: 'system' | 'user' | 'assistant'; content: string }>,
124
+ tools?: Array<{ name: string; description?: string; parameters: Record<string, unknown> }>,
125
+ options?: BackendOptions,
126
+ ): LLMEventStream {
127
+ const response = await fetch(`${this.baseUrl}/api/chat`, {
128
+ method: 'POST',
129
+ headers: { 'Content-Type': 'application/json' },
130
+ body: JSON.stringify({
131
+ model: this.model,
132
+ messages,
133
+ stream: true,
134
+ tools: tools?.map((t) => ({
135
+ type: 'function',
136
+ function: {
137
+ name: t.name,
138
+ description: t.description ?? '',
139
+ parameters: t.parameters,
140
+ },
141
+ })),
142
+ ...options,
143
+ }),
144
+ });
145
+
146
+ if (!response.ok) {
147
+ yield { type: 'error', error: `Ollama HTTP ${response.status}: ${response.statusText}` };
148
+ return;
149
+ }
150
+
151
+ const reader = response.body?.getReader();
152
+ if (!reader) {
153
+ yield { type: 'error', error: 'Ollama response has no body' };
154
+ return;
155
+ }
156
+
157
+ let buffer = '';
158
+ let toolCallBuffer = '';
159
+ let toolCallName = '';
160
+ let toolCallId = '';
161
+
162
+ try {
163
+ while (true) {
164
+ const { done, value } = await reader.read();
165
+ if (done) break;
166
+
167
+ buffer += new TextDecoder().decode(value, { stream: true });
168
+
169
+ const lines = buffer.split('\n');
170
+ buffer = lines.pop() ?? '';
171
+
172
+ for (const line of lines) {
173
+ if (!line.trim()) continue;
174
+ const json = JSON.parse(line);
175
+
176
+ if (json.message?.role === 'assistant') {
177
+ const content = json.message.content;
178
+ if (content) {
179
+ yield { type: 'chunk', content };
180
+ }
181
+
182
+ // Detect tool calls in the message
183
+ if (json.message.tool_calls) {
184
+ for (const tc of json.message.tool_calls) {
185
+ toolCallId = tc.id ?? `tool_${Date.now()}`;
186
+ toolCallName = tc.function?.name ?? '';
187
+ toolCallBuffer += tc.function?.arguments ?? '';
188
+ }
189
+ }
190
+ }
191
+
192
+ // Accumulate tool call arguments
193
+ if (toolCallName && json.message?.tool_calls) {
194
+ for (const tc of json.message.tool_calls) {
195
+ if (tc.function?.name === toolCallName) {
196
+ toolCallBuffer += tc.function?.arguments ?? '';
197
+ }
198
+ }
199
+ }
200
+ }
201
+ }
202
+ } finally {
203
+ reader.releaseLock();
204
+ }
205
+
206
+ // Emit tool-call event if we accumulated arguments
207
+ if (toolCallName && toolCallBuffer) {
208
+ yield {
209
+ type: 'tool-call',
210
+ toolCall: { id: toolCallId, name: toolCallName, args: toolCallBuffer },
211
+ };
212
+ }
213
+
214
+ yield { type: 'finish', finishReason: 'stop' };
215
+ }
216
+
217
+ async healthCheck(): Promise<boolean> {
218
+ try {
219
+ const res = await fetch(`${this.baseUrl}/api/tags`, { signal: AbortSignal.timeout(5000) });
220
+ return res.ok;
221
+ } catch {
222
+ return false;
223
+ }
224
+ }
225
+ }
226
+
227
+ // ─── OpenAI backend adapter ─────────────────────────────────────────────────
228
+
229
+ export class OpenAIBackend implements LLMBackend {
230
+ readonly id = 'openai';
231
+ readonly name = 'OpenAI';
232
+
233
+ constructor(
234
+ private readonly model: string,
235
+ private readonly apiKey: string,
236
+ private readonly baseUrl: string = 'https://api.openai.com/v1',
237
+ ) {}
238
+
239
+ async *complete(
240
+ messages: Array<{ role: 'system' | 'user' | 'assistant'; content: string }>,
241
+ tools?: Array<{ name: string; description?: string; parameters: Record<string, unknown> }>,
242
+ options?: BackendOptions,
243
+ ): LLMEventStream {
244
+ const response = await fetch(`${this.baseUrl}/chat/completions`, {
245
+ method: 'POST',
246
+ headers: {
247
+ 'Content-Type': 'application/json',
248
+ Authorization: `Bearer ${this.apiKey}`,
249
+ },
250
+ body: JSON.stringify({
251
+ model: this.model,
252
+ messages,
253
+ stream: true,
254
+ tools: tools?.map((t) => ({
255
+ type: 'function',
256
+ function: {
257
+ name: t.name,
258
+ description: t.description ?? '',
259
+ parameters: t.parameters,
260
+ },
261
+ })),
262
+ ...options,
263
+ }),
264
+ });
265
+
266
+ if (!response.ok) {
267
+ const errBody = await response.text();
268
+ yield { type: 'error', error: `OpenAI HTTP ${response.status}: ${errBody}` };
269
+ return;
270
+ }
271
+
272
+ const reader = response.body?.getReader();
273
+ if (!reader) {
274
+ yield { type: 'error', error: 'OpenAI response has no body' };
275
+ return;
276
+ }
277
+
278
+ let buffer = '';
279
+ let accumulatedArgs = '';
280
+ let currentToolId = '';
281
+ let currentToolName = '';
282
+
283
+ try {
284
+ while (true) {
285
+ const { done, value } = await reader.read();
286
+ if (done) break;
287
+
288
+ buffer += new TextDecoder().decode(value, { stream: true });
289
+
290
+ const lines = buffer.split('\n');
291
+ buffer = lines.pop() ?? '';
292
+
293
+ for (const line of lines) {
294
+ if (!line.trim() || !line.startsWith('data: ')) continue;
295
+ const data = line.slice(6);
296
+ if (data === '[DONE]') continue;
297
+
298
+ const json = JSON.parse(data);
299
+ const delta = json.choices?.[0]?.delta;
300
+
301
+ if (delta?.content) {
302
+ yield { type: 'chunk', content: delta.content };
303
+ }
304
+
305
+ if (delta?.tool_calls?.[0]) {
306
+ const tc = delta.tool_calls[0];
307
+ if (tc.id) currentToolId = tc.id;
308
+ if (tc.function?.name) currentToolName = tc.function.name;
309
+ accumulatedArgs += tc.function?.arguments ?? '';
310
+ }
311
+ }
312
+ }
313
+ } finally {
314
+ reader.releaseLock();
315
+ }
316
+
317
+ if (currentToolName && accumulatedArgs) {
318
+ yield {
319
+ type: 'tool-call',
320
+ toolCall: { id: currentToolId, name: currentToolName, args: accumulatedArgs },
321
+ };
322
+ }
323
+
324
+ yield { type: 'finish', finishReason: 'stop' };
325
+ }
326
+
327
+ async healthCheck(): Promise<boolean> {
328
+ try {
329
+ const res = await fetch(`${this.baseUrl}/models`, {
330
+ headers: { Authorization: `Bearer ${this.apiKey}` },
331
+ signal: AbortSignal.timeout(5000),
332
+ });
333
+ return res.ok;
334
+ } catch {
335
+ return false;
336
+ }
337
+ }
338
+ }
339
+
340
+ // ─── vLLM backend adapter ───────────────────────────────────────────────────
341
+
342
+ export class vLLMBackend implements LLMBackend {
343
+ readonly id = 'vllm';
344
+ readonly name = 'vLLM';
345
+
346
+ constructor(
347
+ private readonly model: string,
348
+ private readonly baseUrl: string = 'http://localhost:8000/v1',
349
+ ) {}
350
+
351
+ async *complete(
352
+ messages: Array<{ role: 'system' | 'user' | 'assistant'; content: string }>,
353
+ tools?: Array<{ name: string; description?: string; parameters: Record<string, unknown> }>,
354
+ options?: BackendOptions,
355
+ ): LLMEventStream {
356
+ const response = await fetch(`${this.baseUrl}/chat/completions`, {
357
+ method: 'POST',
358
+ headers: { 'Content-Type': 'application/json' },
359
+ body: JSON.stringify({
360
+ model: this.model,
361
+ messages,
362
+ stream: true,
363
+ tools: tools?.map((t) => ({
364
+ type: 'function',
365
+ function: {
366
+ name: t.name,
367
+ description: t.description ?? '',
368
+ parameters: t.parameters,
369
+ },
370
+ })),
371
+ ...options,
372
+ }),
373
+ });
374
+
375
+ if (!response.ok) {
376
+ yield { type: 'error', error: `vLLM HTTP ${response.status}: ${await response.text()}` };
377
+ return;
378
+ }
379
+
380
+ const reader = response.body?.getReader();
381
+ if (!reader) {
382
+ yield { type: 'error', error: 'vLLM response has no body' };
383
+ return;
384
+ }
385
+
386
+ let buffer = '';
387
+ let accumulatedArgs = '';
388
+ let currentToolId = '';
389
+ let currentToolName = '';
390
+
391
+ try {
392
+ while (true) {
393
+ const { done, value } = await reader.read();
394
+ if (done) break;
395
+
396
+ buffer += new TextDecoder().decode(value, { stream: true });
397
+
398
+ const lines = buffer.split('\n');
399
+ buffer = lines.pop() ?? '';
400
+
401
+ for (const line of lines) {
402
+ if (!line.trim() || !line.startsWith('data: ')) continue;
403
+ const data = line.slice(6);
404
+ if (data === '[DONE]') continue;
405
+
406
+ const json = JSON.parse(data);
407
+ const delta = json.choices?.[0]?.delta;
408
+
409
+ if (delta?.content) {
410
+ yield { type: 'chunk', content: delta.content };
411
+ }
412
+
413
+ if (delta?.tool_calls?.[0]) {
414
+ const tc = delta.tool_calls[0];
415
+ if (tc.id) currentToolId = tc.id;
416
+ if (tc.function?.name) currentToolName = tc.function.name;
417
+ accumulatedArgs += tc.function?.arguments ?? '';
418
+ }
419
+ }
420
+ }
421
+ } finally {
422
+ reader.releaseLock();
423
+ }
424
+
425
+ if (currentToolName && accumulatedArgs) {
426
+ yield {
427
+ type: 'tool-call',
428
+ toolCall: { id: currentToolId, name: currentToolName, args: accumulatedArgs },
429
+ };
430
+ }
431
+
432
+ yield { type: 'finish', finishReason: 'stop' };
433
+ }
434
+
435
+ async healthCheck(): Promise<boolean> {
436
+ try {
437
+ const res = await fetch(`${this.baseUrl}/models`, { signal: AbortSignal.timeout(5000) });
438
+ return res.ok;
439
+ } catch {
440
+ return false;
441
+ }
442
+ }
443
+ }