lynkr 9.14.1 → 9.14.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.
Files changed (96) hide show
  1. package/README.md +5 -1
  2. package/bin/cli.js +14 -6
  3. package/bin/lynkr-desktop-token.js +108 -0
  4. package/bin/lynkr-init.js +13 -1
  5. package/bin/lynkr-restart.js +125 -0
  6. package/bin/opencode-setup.js +175 -0
  7. package/bin/run.js +92 -0
  8. package/config/model-capabilities.json +20 -0
  9. package/config/model-capability-seeds.json +75 -0
  10. package/config/model-tiers.json +13 -1
  11. package/package.json +16 -22
  12. package/scripts/claude-desktop.js +212 -0
  13. package/scripts/ope-report.js +78 -0
  14. package/scripts/seed-capabilities.js +230 -0
  15. package/src/api/claude-desktop-gateway.js +123 -0
  16. package/src/api/health.js +0 -1
  17. package/src/api/mcp-broker.js +149 -0
  18. package/src/api/middleware/budget-enforcer.js +77 -7
  19. package/src/api/middleware/budget.js +36 -2
  20. package/src/api/middleware/session.js +0 -0
  21. package/src/api/openai-router.js +210 -15
  22. package/src/api/providers-handler.js +16 -1
  23. package/src/api/router.js +73 -106
  24. package/src/auth-mode.js +40 -4
  25. package/src/budget/index.js +86 -0
  26. package/src/cache/embeddings.js +125 -35
  27. package/src/cache/onnx-embedder.js +92 -0
  28. package/src/cache/prompt.js +0 -7
  29. package/src/clients/bedrock-utils.js +2 -1
  30. package/src/clients/codex-process.js +1 -1
  31. package/src/clients/codex-utils.js +0 -2
  32. package/src/clients/databricks.js +337 -46
  33. package/src/clients/gpt-utils.js +84 -8
  34. package/src/clients/health-probe.js +157 -0
  35. package/src/clients/openai-format.js +73 -23
  36. package/src/clients/provider-capabilities.js +27 -2
  37. package/src/clients/resilience.js +0 -1
  38. package/src/clients/responses-format.js +0 -56
  39. package/src/clients/routing.js +1 -1
  40. package/src/config/index.js +13 -2
  41. package/src/context/budget.js +1 -1
  42. package/src/context/compression.js +127 -19
  43. package/src/context/tool-result-compressor.js +3 -5
  44. package/src/dashboard/api.js +1 -1
  45. package/src/logger/oversized-error-stream.js +1 -1
  46. package/src/memory/extractor.js +3 -3
  47. package/src/memory/search.js +2 -2
  48. package/src/memory/store.js +9 -24
  49. package/src/memory/tools.js +14 -0
  50. package/src/observability/metrics.js +52 -3
  51. package/src/observability/otel.js +180 -0
  52. package/src/orchestrator/azure-responses-sse.js +223 -0
  53. package/src/orchestrator/index.js +216 -212
  54. package/src/orchestrator/passthrough-stream.js +7 -1
  55. package/src/orchestrator/sse-transformer.js +130 -13
  56. package/src/policy/safe-commands.js +0 -1
  57. package/src/routing/bandit.js +39 -0
  58. package/src/routing/calibration.js +21 -1
  59. package/src/routing/capabilities.js +88 -0
  60. package/src/routing/capability-seeds/benchmarks-dir.js +58 -0
  61. package/src/routing/capability-seeds/family-heuristics.js +77 -0
  62. package/src/routing/capability-seeds/family.js +105 -0
  63. package/src/routing/capability-seeds/models-dev.js +64 -0
  64. package/src/routing/capability-seeds/normalize.js +160 -0
  65. package/src/routing/capability-seeds/registry.js +125 -0
  66. package/src/routing/capability-seeds/sources.js +69 -0
  67. package/src/routing/capability-seeds/swebench.js +66 -0
  68. package/src/routing/client-profiles.js +26 -0
  69. package/src/routing/complexity-analyzer.js +5 -8
  70. package/src/routing/confidence-scorer.js +2 -2
  71. package/src/routing/cost-optimizer.js +1 -2
  72. package/src/routing/decide.js +149 -0
  73. package/src/routing/deescalator.js +38 -1
  74. package/src/routing/embedding-cache.js +0 -1
  75. package/src/routing/index.js +202 -86
  76. package/src/routing/intent-score.js +0 -1
  77. package/src/routing/knn-router.js +27 -5
  78. package/src/routing/latency-tracker.js +1 -1
  79. package/src/routing/model-registry.js +25 -0
  80. package/src/routing/model-slots.js +65 -0
  81. package/src/routing/model-tiers.js +2 -0
  82. package/src/routing/ope.js +255 -0
  83. package/src/routing/openai-model-slots.js +82 -0
  84. package/src/routing/regret-estimator.js +1 -1
  85. package/src/routing/shortfall.js +290 -0
  86. package/src/routing/stuck-detector.js +127 -0
  87. package/src/routing/telemetry.js +11 -2
  88. package/src/server.js +41 -3
  89. package/src/stores/file-store.js +5 -1
  90. package/src/tools/web-search-exec.js +182 -0
  91. package/src/training/trajectory-compressor.js +1 -1
  92. package/src/workers/pool.js +1 -1
  93. package/src/workers/worker.js +1 -1
  94. package/scripts/convert-routellm.py +0 -105
  95. package/src/diff/comments.js +0 -153
  96. package/src/policy/web-fallback.js +0 -33
@@ -0,0 +1,223 @@
1
+ /**
2
+ * Azure OpenAI Responses API — streaming adapter.
3
+ *
4
+ * The Responses API's streaming event vocabulary (response.created,
5
+ * response.output_item.added, response.content_part.added,
6
+ * response.output_text.delta/.done, response.function_call_arguments.delta/
7
+ * .done, response.output_item.done, response.completed) is structurally
8
+ * different from OpenAI Chat Completions' generic `choices[0].delta` chunk
9
+ * shape, which is what sse-transformer.js's `_openaiToAnthropicEvents`
10
+ * understands.
11
+ *
12
+ * Rather than duplicate that function's Anthropic-SSE emission logic (badge
13
+ * injection, message_start/message_stop, the mid-stream-error and
14
+ * no-finish-reason safety branches, usage extraction) for a second wire
15
+ * format, this module translates Responses-API events into SYNTHETIC OpenAI
16
+ * Chat-Completions SSE TEXT (`data: {"choices":[...]}\n\n`) and hands that
17
+ * off unchanged to the existing, already-tested transformer. Net effect:
18
+ * `invokeAzureOpenAI` returns a stream from this module exactly the way
19
+ * invokeMoonshot/invokeBaidu/invokeOpenAI already return a raw upstream
20
+ * stream — the orchestrator's existing `sseTransform.openaiToAnthropicSSE`
21
+ * call site needs zero changes to consume it.
22
+ *
23
+ * Verified against two live captures from this deployment's actual Azure
24
+ * endpoint (2026-08-26, api-version=2025-04-01-preview, model gpt-5.6-sol):
25
+ * one plain-text response, one forced tool-call response. Re-verify against
26
+ * a live capture if Azure's preview API version changes this shape.
27
+ *
28
+ * Tool-call dedup: the buffered Responses-API conversion in databricks.js
29
+ * (invokeAzureOpenAI) filters out exact duplicate {name, arguments}
30
+ * function-call signatures within one response (an observed GPT-5.x quirk).
31
+ * To preserve that guarantee here without needing the FULL response in hand
32
+ * first, this module buffers a function_call's argument fragments locally
33
+ * (never emitting them) until that call's response.function_call_arguments.done
34
+ * fires, checks the signature, and only then emits the accumulated deltas —
35
+ * as one burst — or silently drops them if it's a repeat. This means
36
+ * function-call arguments have a small, per-call delay (between that call's
37
+ * first and last argument fragment) while text streams fully live with zero
38
+ * delay — a deliberate tradeoff to keep the existing dedup guarantee, not an
39
+ * oversight.
40
+ */
41
+
42
+ const logger = require("../logger");
43
+ const { _sseDataLines } = require("./sse-transformer");
44
+
45
+ /**
46
+ * @param {ReadableStream|AsyncIterable} rawResponsesStream - raw Azure
47
+ * Responses API SSE byte stream (stream:true was set on the request).
48
+ * @param {Object} [opts]
49
+ * @param {string} [opts.model] - fallback model id for synthesized chunks.
50
+ * @returns {ReadableStream} synthetic OpenAI Chat-Completions SSE byte stream.
51
+ */
52
+ function azureResponsesToOpenAIChunks(rawResponsesStream, opts = {}) {
53
+ const generator = _toOpenAIChunkText(rawResponsesStream, opts);
54
+ const encoder = new TextEncoder();
55
+ return new ReadableStream({
56
+ async pull(controller) {
57
+ try {
58
+ const { value, done } = await generator.next();
59
+ if (done) {
60
+ controller.close();
61
+ return;
62
+ }
63
+ controller.enqueue(encoder.encode(value));
64
+ } catch (err) {
65
+ controller.error(err);
66
+ }
67
+ },
68
+ async cancel() {
69
+ try { await generator.return(); } catch { /* already finished */ }
70
+ },
71
+ });
72
+ }
73
+
74
+ async function* _toOpenAIChunkText(rawResponsesStream, opts) {
75
+ const id = `chatcmpl-azresp-${Date.now()}`;
76
+ const created = Math.floor(Date.now() / 1000);
77
+ const model = opts.model || "gpt-5.6-sol";
78
+
79
+ const chunk = (delta, extra = {}) => `data: ${JSON.stringify({
80
+ id, object: "chat.completion.chunk", created, model,
81
+ choices: [{ index: 0, delta, finish_reason: null }],
82
+ ...extra,
83
+ })}\n\n`;
84
+
85
+ const finishChunk = (finishReason, usage) => `data: ${JSON.stringify({
86
+ id, object: "chat.completion.chunk", created, model,
87
+ choices: [{ index: 0, delta: {}, finish_reason: finishReason }],
88
+ ...(usage ? { usage } : {}),
89
+ })}\n\n`;
90
+
91
+ // output_index -> {kind: "message"|"function_call", openaiToolIndex,
92
+ // name, callId, argBuf: string}. argBuf only used for function_call.
93
+ const items = new Map();
94
+ let nextToolIndex = 0;
95
+ const seenToolSignatures = new Set();
96
+ let sawFunctionCall = false;
97
+ let sawAnyOutput = false;
98
+
99
+ try {
100
+ for await (const payload of _sseDataLines(rawResponsesStream)) {
101
+ if (payload === "[DONE]") break;
102
+ let ev;
103
+ try { ev = JSON.parse(payload); } catch { continue; }
104
+
105
+ switch (ev.type) {
106
+ case "response.output_item.added": {
107
+ const item = ev.item || {};
108
+ if (item.type === "function_call") {
109
+ items.set(ev.output_index, {
110
+ kind: "function_call",
111
+ openaiToolIndex: nextToolIndex++,
112
+ name: item.name || "",
113
+ callId: item.call_id || item.id || "",
114
+ argBuf: "",
115
+ });
116
+ } else {
117
+ items.set(ev.output_index, { kind: "message" });
118
+ }
119
+ break;
120
+ }
121
+
122
+ case "response.output_text.delta": {
123
+ sawAnyOutput = true;
124
+ if (ev.delta) yield chunk({ content: ev.delta });
125
+ break;
126
+ }
127
+
128
+ case "response.function_call_arguments.delta": {
129
+ const state = items.get(ev.output_index);
130
+ if (state && state.kind === "function_call") {
131
+ state.argBuf += ev.delta || "";
132
+ }
133
+ break;
134
+ }
135
+
136
+ case "response.function_call_arguments.done": {
137
+ const state = items.get(ev.output_index);
138
+ if (!state || state.kind !== "function_call") break;
139
+ sawAnyOutput = true;
140
+ sawFunctionCall = true;
141
+ const finalArgs = typeof ev.arguments === "string" ? ev.arguments : state.argBuf;
142
+ const signature = `${state.name}:${finalArgs}`;
143
+ if (seenToolSignatures.has(signature)) {
144
+ logger.warn({
145
+ name: state.name,
146
+ argsPreview: finalArgs.slice(0, 120),
147
+ }, "[AzureResponsesSSE] Filtered duplicate streamed tool call");
148
+ break;
149
+ }
150
+ seenToolSignatures.add(signature);
151
+ // First delta for this tool carries id/name (empty arguments);
152
+ // second carries the complete arguments as one fragment — matches
153
+ // real OpenAI streaming shape, which sse-transformer.js's toolAcc
154
+ // merge logic already expects (id/name first-non-null-wins, args
155
+ // concatenated across fragments).
156
+ yield chunk({
157
+ tool_calls: [{
158
+ index: state.openaiToolIndex,
159
+ id: state.callId,
160
+ type: "function",
161
+ function: { name: state.name, arguments: "" },
162
+ }],
163
+ });
164
+ yield chunk({
165
+ tool_calls: [{
166
+ index: state.openaiToolIndex,
167
+ function: { arguments: finalArgs },
168
+ }],
169
+ });
170
+ break;
171
+ }
172
+
173
+ case "response.completed": {
174
+ const usageRaw = ev.response?.usage;
175
+ const usage = usageRaw ? {
176
+ prompt_tokens: usageRaw.input_tokens ?? 0,
177
+ completion_tokens: usageRaw.output_tokens ?? 0,
178
+ total_tokens: usageRaw.total_tokens
179
+ ?? ((usageRaw.input_tokens ?? 0) + (usageRaw.output_tokens ?? 0)),
180
+ prompt_tokens_details: {
181
+ cached_tokens: usageRaw.input_tokens_details?.cached_tokens ?? 0,
182
+ },
183
+ } : undefined;
184
+ const finishReason = sawFunctionCall ? "tool_calls" : "stop";
185
+ yield finishChunk(finishReason, usage);
186
+ yield "data: [DONE]\n\n";
187
+ return;
188
+ }
189
+
190
+ case "response.failed":
191
+ case "response.incomplete": {
192
+ logger.warn({
193
+ type: ev.type,
194
+ error: ev.response?.error || ev.error || null,
195
+ }, "[AzureResponsesSSE] Upstream reported failure mid-stream");
196
+ // No finish_reason chunk, no [DONE] — matches sse-transformer.js's
197
+ // own no-finish-reason safety branch (its EOF-without-finish_reason
198
+ // case), which synthesizes an Anthropic error event rather than a
199
+ // false-success message_stop when downstream sees the SSE end here.
200
+ return;
201
+ }
202
+
203
+ default:
204
+ break; // response.created/.in_progress, content_part.*, output_item.done — no chunk needed
205
+ }
206
+ }
207
+ } catch (err) {
208
+ logger.warn({ err: err.message }, "[AzureResponsesSSE] Upstream stream failed mid-flight");
209
+ return;
210
+ }
211
+
212
+ // Stream ended without response.completed and without sawAnyOutput ever
213
+ // firing a finish — same "don't fake success" discipline as above.
214
+ if (!sawAnyOutput) {
215
+ logger.warn("[AzureResponsesSSE] Upstream stream ended with no output and no response.completed");
216
+ }
217
+ }
218
+
219
+ module.exports = {
220
+ azureResponsesToOpenAIChunks,
221
+ // Exported for unit tests.
222
+ _toOpenAIChunkText,
223
+ };