@jxburros/ai-nugget 0.4.1 → 0.7.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/README.md +158 -382
- package/UPGRADING.md +182 -0
- package/dist/adapters/engines/anthropic.d.ts +7 -1
- package/dist/adapters/engines/anthropic.js +82 -37
- package/dist/adapters/engines/base.d.ts +29 -2
- package/dist/adapters/engines/base.js +52 -8
- package/dist/adapters/engines/google.d.ts +7 -1
- package/dist/adapters/engines/google.js +80 -29
- package/dist/adapters/engines/ollama.d.ts +2 -1
- package/dist/adapters/engines/ollama.js +57 -15
- package/dist/adapters/engines/openaiChat.d.ts +8 -1
- package/dist/adapters/engines/openaiChat.js +109 -29
- package/dist/adapters/profiles.d.ts +21 -1
- package/dist/adapters/profiles.js +12 -2
- package/dist/agent/loop.d.ts +45 -1
- package/dist/agent/loop.js +117 -12
- package/dist/cjs/adapters/engines/anthropic.js +326 -0
- package/dist/cjs/adapters/engines/base.js +147 -0
- package/dist/cjs/adapters/engines/google.js +291 -0
- package/dist/cjs/adapters/engines/ollama.js +205 -0
- package/dist/cjs/adapters/engines/openaiChat.js +312 -0
- package/dist/cjs/adapters/index.js +22 -0
- package/dist/cjs/adapters/profiles.js +125 -0
- package/dist/cjs/agent/index.js +18 -0
- package/dist/cjs/agent/loop.js +373 -0
- package/dist/cjs/agent/tools.js +51 -0
- package/dist/cjs/connect.js +27 -0
- package/dist/cjs/errors.js +110 -0
- package/dist/cjs/handler.js +586 -0
- package/dist/cjs/index.js +28 -0
- package/dist/cjs/json.js +121 -0
- package/dist/cjs/keys.js +81 -0
- package/dist/cjs/package.json +3 -0
- package/dist/cjs/policy.js +42 -0
- package/dist/cjs/reasoning.js +159 -0
- package/dist/cjs/redact.js +78 -0
- package/dist/cjs/tokens.js +22 -0
- package/dist/cjs/transport.js +172 -0
- package/dist/cjs/types.js +6 -0
- package/dist/cjs/util.js +126 -0
- package/dist/errors.js +25 -1
- package/dist/handler.d.ts +67 -1
- package/dist/handler.js +266 -61
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/reasoning.d.ts +45 -0
- package/dist/reasoning.js +154 -0
- package/dist/redact.d.ts +18 -0
- package/dist/redact.js +29 -1
- package/dist/transport.d.ts +3 -1
- package/dist/transport.js +21 -1
- package/dist/types.d.ts +112 -2
- package/dist/util.d.ts +22 -0
- package/dist/util.js +59 -0
- package/nugget/VERSION.txt +2 -2
- package/nugget/dist/adapters/engines/anthropic.d.ts +14 -0
- package/nugget/dist/adapters/engines/anthropic.d.ts.map +1 -0
- package/nugget/dist/adapters/engines/anthropic.js +323 -0
- package/nugget/dist/adapters/engines/anthropic.js.map +1 -0
- package/nugget/dist/adapters/engines/base.d.ts +50 -0
- package/nugget/dist/adapters/engines/base.d.ts.map +1 -0
- package/nugget/dist/adapters/engines/base.js +137 -0
- package/nugget/dist/adapters/engines/base.js.map +1 -0
- package/nugget/dist/adapters/engines/google.d.ts +24 -0
- package/nugget/dist/adapters/engines/google.d.ts.map +1 -0
- package/nugget/dist/adapters/engines/google.js +288 -0
- package/nugget/dist/adapters/engines/google.js.map +1 -0
- package/nugget/dist/adapters/engines/ollama.d.ts +11 -0
- package/nugget/dist/adapters/engines/ollama.d.ts.map +1 -0
- package/nugget/dist/adapters/engines/ollama.js +202 -0
- package/nugget/dist/adapters/engines/ollama.js.map +1 -0
- package/nugget/dist/adapters/engines/openaiChat.d.ts +22 -0
- package/nugget/dist/adapters/engines/openaiChat.d.ts.map +1 -0
- package/nugget/dist/adapters/engines/openaiChat.js +308 -0
- package/nugget/dist/adapters/engines/openaiChat.js.map +1 -0
- package/nugget/dist/adapters/index.d.ts +5 -0
- package/nugget/dist/adapters/index.d.ts.map +1 -0
- package/nugget/dist/adapters/index.js +17 -0
- package/nugget/dist/adapters/index.js.map +1 -0
- package/nugget/dist/adapters/profiles.d.ts +62 -0
- package/nugget/dist/adapters/profiles.d.ts.map +1 -0
- package/nugget/dist/adapters/profiles.js +121 -0
- package/nugget/dist/adapters/profiles.js.map +1 -0
- package/nugget/dist/agent/index.d.ts +3 -0
- package/nugget/dist/agent/index.d.ts.map +1 -0
- package/nugget/dist/agent/index.js +3 -0
- package/nugget/dist/agent/index.js.map +1 -0
- package/nugget/dist/agent/loop.d.ts +114 -0
- package/nugget/dist/agent/loop.d.ts.map +1 -0
- package/nugget/dist/agent/loop.js +371 -0
- package/nugget/dist/agent/loop.js.map +1 -0
- package/nugget/dist/agent/tools.d.ts +29 -0
- package/nugget/dist/agent/tools.d.ts.map +1 -0
- package/nugget/dist/agent/tools.js +48 -0
- package/nugget/dist/agent/tools.js.map +1 -0
- package/nugget/dist/cjs/adapters/engines/anthropic.js +326 -0
- package/nugget/dist/cjs/adapters/engines/base.js +147 -0
- package/nugget/dist/cjs/adapters/engines/google.js +291 -0
- package/nugget/dist/cjs/adapters/engines/ollama.js +205 -0
- package/nugget/dist/cjs/adapters/engines/openaiChat.js +312 -0
- package/nugget/dist/cjs/adapters/index.js +22 -0
- package/nugget/dist/cjs/adapters/profiles.js +125 -0
- package/nugget/dist/cjs/agent/index.js +18 -0
- package/nugget/dist/cjs/agent/loop.js +373 -0
- package/nugget/dist/cjs/agent/tools.js +51 -0
- package/nugget/dist/cjs/connect.js +27 -0
- package/nugget/dist/cjs/errors.js +110 -0
- package/nugget/dist/cjs/handler.js +586 -0
- package/nugget/dist/cjs/index.js +28 -0
- package/nugget/dist/cjs/json.js +121 -0
- package/nugget/dist/cjs/keys.js +81 -0
- package/nugget/dist/cjs/package.json +3 -0
- package/nugget/dist/cjs/policy.js +42 -0
- package/nugget/dist/cjs/reasoning.js +159 -0
- package/nugget/dist/cjs/redact.js +78 -0
- package/nugget/dist/cjs/tokens.js +22 -0
- package/nugget/dist/cjs/transport.js +172 -0
- package/nugget/dist/cjs/types.js +6 -0
- package/nugget/dist/cjs/util.js +126 -0
- package/nugget/dist/connect.d.ts +31 -0
- package/nugget/dist/connect.d.ts.map +1 -0
- package/nugget/dist/connect.js +25 -0
- package/nugget/dist/connect.js.map +1 -0
- package/nugget/dist/errors.d.ts +22 -0
- package/nugget/dist/errors.d.ts.map +1 -0
- package/nugget/dist/errors.js +104 -0
- package/nugget/dist/errors.js.map +1 -0
- package/nugget/dist/handler.d.ts +114 -0
- package/nugget/dist/handler.d.ts.map +1 -0
- package/nugget/dist/handler.js +582 -0
- package/nugget/dist/handler.js.map +1 -0
- package/nugget/dist/index.d.ts +13 -0
- package/nugget/dist/index.d.ts.map +1 -0
- package/nugget/dist/index.js +13 -0
- package/nugget/dist/index.js.map +1 -0
- package/nugget/dist/json.d.ts +7 -0
- package/nugget/dist/json.d.ts.map +1 -0
- package/nugget/dist/json.js +114 -0
- package/nugget/dist/json.js.map +1 -0
- package/nugget/dist/keys.d.ts +7 -0
- package/nugget/dist/keys.d.ts.map +1 -0
- package/nugget/dist/keys.js +75 -0
- package/nugget/dist/keys.js.map +1 -0
- package/nugget/dist/policy.d.ts +6 -0
- package/nugget/dist/policy.d.ts.map +1 -0
- package/nugget/dist/policy.js +37 -0
- package/nugget/dist/policy.js.map +1 -0
- package/nugget/dist/reasoning.d.ts +45 -0
- package/nugget/dist/reasoning.d.ts.map +1 -0
- package/nugget/dist/reasoning.js +154 -0
- package/nugget/dist/reasoning.js.map +1 -0
- package/nugget/dist/redact.d.ts +26 -0
- package/nugget/dist/redact.d.ts.map +1 -0
- package/nugget/dist/redact.js +74 -0
- package/nugget/dist/redact.js.map +1 -0
- package/nugget/dist/tokens.d.ts +5 -0
- package/nugget/dist/tokens.d.ts.map +1 -0
- package/nugget/dist/tokens.js +18 -0
- package/nugget/dist/tokens.js.map +1 -0
- package/nugget/dist/transport.d.ts +30 -0
- package/nugget/dist/transport.d.ts.map +1 -0
- package/nugget/dist/transport.js +165 -0
- package/nugget/dist/transport.js.map +1 -0
- package/nugget/dist/types.d.ts +301 -0
- package/nugget/dist/types.d.ts.map +1 -0
- package/nugget/dist/types.js +4 -0
- package/nugget/dist/types.js.map +1 -0
- package/nugget/dist/util.d.ts +31 -0
- package/nugget/dist/util.d.ts.map +1 -0
- package/nugget/dist/util.js +114 -0
- package/nugget/dist/util.js.map +1 -0
- package/nugget/src/adapters/engines/anthropic.ts +78 -37
- package/nugget/src/adapters/engines/base.ts +50 -7
- package/nugget/src/adapters/engines/google.ts +79 -29
- package/nugget/src/adapters/engines/ollama.ts +56 -17
- package/nugget/src/adapters/engines/openaiChat.ts +104 -30
- package/nugget/src/adapters/profiles.ts +53 -3
- package/nugget/src/agent/loop.ts +156 -13
- package/nugget/src/errors.ts +25 -1
- package/nugget/src/handler.ts +308 -59
- package/nugget/src/index.ts +1 -0
- package/nugget/src/reasoning.ts +174 -0
- package/nugget/src/redact.ts +32 -1
- package/nugget/src/transport.ts +20 -1
- package/nugget/src/types.ts +113 -1
- package/nugget/src/util.ts +64 -0
- package/package.json +18 -5
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.OpenAIChatAdapter = void 0;
|
|
4
|
+
exports.shouldRetryWithoutReasoningEffort = shouldRetryWithoutReasoningEffort;
|
|
5
|
+
const errors_js_1 = require("../../errors.js");
|
|
6
|
+
const tokens_js_1 = require("../../tokens.js");
|
|
7
|
+
const transport_js_1 = require("../../transport.js");
|
|
8
|
+
const util_js_1 = require("../../util.js");
|
|
9
|
+
const base_js_1 = require("./base.js");
|
|
10
|
+
class OpenAIChatAdapter {
|
|
11
|
+
profile;
|
|
12
|
+
provider;
|
|
13
|
+
constructor(provider, profile) {
|
|
14
|
+
this.profile = profile;
|
|
15
|
+
this.provider = provider;
|
|
16
|
+
}
|
|
17
|
+
async chat(conn, req) {
|
|
18
|
+
let final;
|
|
19
|
+
for await (const event of this.stream(conn, req)) {
|
|
20
|
+
if (event.type === 'done')
|
|
21
|
+
final = event.result;
|
|
22
|
+
if (event.type === 'error')
|
|
23
|
+
throw event.error;
|
|
24
|
+
}
|
|
25
|
+
if (!final)
|
|
26
|
+
throw new errors_js_1.AIError('Provider stream ended without a result', { kind: 'invalid_response', provider: conn.provider });
|
|
27
|
+
return final;
|
|
28
|
+
}
|
|
29
|
+
async *stream(conn, req) {
|
|
30
|
+
const started = Date.now();
|
|
31
|
+
let firstTokenMs = null;
|
|
32
|
+
let text = '';
|
|
33
|
+
let toolCalls = [];
|
|
34
|
+
let inputTokens;
|
|
35
|
+
let outputTokens;
|
|
36
|
+
let finish;
|
|
37
|
+
let sawTerminal = false;
|
|
38
|
+
const timeout = (0, base_js_1.streamTimeout)(conn, req.signal);
|
|
39
|
+
yield { type: 'start', callId: '', provider: conn.provider, model: req.model };
|
|
40
|
+
try {
|
|
41
|
+
let res;
|
|
42
|
+
try {
|
|
43
|
+
res = await (0, transport_js_1.postResponse)(urlFor(conn, this.profile, req), openAiBody(req, this.profile), conn.headers, timeout.signal, conn.provider);
|
|
44
|
+
}
|
|
45
|
+
catch (error) {
|
|
46
|
+
// OpenAI's `/chat/completions` refuses function tools on a reasoning model
|
|
47
|
+
// unless `reasoning_effort` is `'none'` ("Function tools with reasoning_effort
|
|
48
|
+
// are not supported … use /v1/responses or set reasoning_effort to 'none'").
|
|
49
|
+
// The nugget never sets `reasoning_effort` itself, so the provider default
|
|
50
|
+
// applies and every tool-using turn 400s. Rather than make each app learn
|
|
51
|
+
// this the hard way, retry the one request once with the effort disabled
|
|
52
|
+
// and say so on the stream. Reactive on purpose: sending
|
|
53
|
+
// `reasoning_effort` up front would 400 on non-reasoning models instead.
|
|
54
|
+
// A caller-requested effort is overridden too — the alternative is a hard
|
|
55
|
+
// 400 with no tool call at all — and the context event says so.
|
|
56
|
+
if (!shouldRetryWithoutReasoningEffort(error, req))
|
|
57
|
+
throw error;
|
|
58
|
+
yield {
|
|
59
|
+
type: 'context',
|
|
60
|
+
kind: 'reasoning_effort_disabled_for_tools',
|
|
61
|
+
data: {
|
|
62
|
+
reason: 'provider rejected function tools with reasoning_effort; retried with reasoning_effort: none',
|
|
63
|
+
requested: req.providerOptions?.reasoning_effort ?? req.reasoningEffort ?? null,
|
|
64
|
+
},
|
|
65
|
+
};
|
|
66
|
+
const retryReq = { ...req, providerOptions: { ...req.providerOptions, reasoning_effort: 'none' } };
|
|
67
|
+
res = await (0, transport_js_1.postResponse)(urlFor(conn, this.profile, retryReq), openAiBody(retryReq, this.profile), conn.headers, timeout.signal, conn.provider);
|
|
68
|
+
}
|
|
69
|
+
const contentType = res.headers.get('content-type') ?? '';
|
|
70
|
+
if (!contentType.includes('text/event-stream')) {
|
|
71
|
+
// Server ignored stream:true (or is a buffered gateway) — recover the whole body.
|
|
72
|
+
const rawText = await res.text().catch(() => '');
|
|
73
|
+
const raw = (0, base_js_1.safeParse)(rawText) ?? { text: rawText };
|
|
74
|
+
const parsed = parseOpenAiResponse(raw);
|
|
75
|
+
text = parsed.text;
|
|
76
|
+
toolCalls = parsed.toolCalls;
|
|
77
|
+
inputTokens = parsed.inputTokens;
|
|
78
|
+
outputTokens = parsed.outputTokens;
|
|
79
|
+
finish = parsed.finish;
|
|
80
|
+
sawTerminal = true;
|
|
81
|
+
if (parsed.reasoning)
|
|
82
|
+
yield { type: 'reasoning', text: parsed.reasoning };
|
|
83
|
+
if (text)
|
|
84
|
+
yield { type: 'delta', text };
|
|
85
|
+
for (const call of toolCalls)
|
|
86
|
+
yield { type: 'tool_call', call };
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
const partialTools = new Map();
|
|
90
|
+
for await (const line of (0, transport_js_1.sseLines)(res)) {
|
|
91
|
+
timeout.bump();
|
|
92
|
+
const chunk = (0, base_js_1.safeParse)(line);
|
|
93
|
+
const record = (0, util_js_1.asRecord)(chunk);
|
|
94
|
+
if (!record)
|
|
95
|
+
continue;
|
|
96
|
+
const usage = (0, util_js_1.asRecord)(record.usage);
|
|
97
|
+
inputTokens = (0, util_js_1.asNumber)(usage?.prompt_tokens) ?? inputTokens;
|
|
98
|
+
outputTokens = (0, util_js_1.asNumber)(usage?.completion_tokens) ?? outputTokens;
|
|
99
|
+
const choices = Array.isArray(record.choices) ? record.choices : [];
|
|
100
|
+
const choice = (0, util_js_1.asRecord)(choices[0]);
|
|
101
|
+
finish = (0, util_js_1.asString)(choice?.finish_reason) ?? finish;
|
|
102
|
+
if (choice?.finish_reason)
|
|
103
|
+
sawTerminal = true;
|
|
104
|
+
const delta = (0, util_js_1.asRecord)(choice?.delta);
|
|
105
|
+
// Reasoning tokens (DeepSeek `reasoning_content`, others `reasoning`)
|
|
106
|
+
// ride their own channel so they don't contaminate the answer text.
|
|
107
|
+
const reasoning = (0, util_js_1.asString)(delta?.reasoning_content) ?? (0, util_js_1.asString)(delta?.reasoning);
|
|
108
|
+
if (reasoning)
|
|
109
|
+
yield { type: 'reasoning', text: reasoning };
|
|
110
|
+
const piece = (0, util_js_1.asString)(delta?.content);
|
|
111
|
+
if (piece) {
|
|
112
|
+
if (firstTokenMs === null)
|
|
113
|
+
firstTokenMs = Date.now() - started;
|
|
114
|
+
text += piece;
|
|
115
|
+
yield { type: 'delta', text: piece };
|
|
116
|
+
}
|
|
117
|
+
const calls = Array.isArray(delta?.tool_calls) ? delta.tool_calls : [];
|
|
118
|
+
for (const callValue of calls) {
|
|
119
|
+
const call = (0, util_js_1.asRecord)(callValue);
|
|
120
|
+
const index = (0, util_js_1.asNumber)(call?.index) ?? 0;
|
|
121
|
+
const current = partialTools.get(index) ?? { raw: '' };
|
|
122
|
+
current.id = (0, util_js_1.asString)(call?.id) ?? current.id;
|
|
123
|
+
const fn = (0, util_js_1.asRecord)(call?.function);
|
|
124
|
+
current.name = (0, util_js_1.asString)(fn?.name) ?? current.name;
|
|
125
|
+
current.raw += (0, util_js_1.asString)(fn?.arguments) ?? '';
|
|
126
|
+
partialTools.set(index, current);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
toolCalls = [...partialTools.values()].filter((call) => call.name).map((call) => ({
|
|
130
|
+
id: call.id ?? (0, base_js_1.randomId)(),
|
|
131
|
+
name: call.name,
|
|
132
|
+
raw: call.raw,
|
|
133
|
+
arguments: (0, base_js_1.parseArgs)(call.raw),
|
|
134
|
+
}));
|
|
135
|
+
for (const call of toolCalls)
|
|
136
|
+
yield { type: 'tool_call', call };
|
|
137
|
+
}
|
|
138
|
+
if (!sawTerminal)
|
|
139
|
+
yield (0, base_js_1.streamAnomaly)('stream ended without a finish_reason');
|
|
140
|
+
const result = makeResult(conn, req, text, toolCalls, finish, started, firstTokenMs, inputTokens, outputTokens);
|
|
141
|
+
yield { type: 'done', result };
|
|
142
|
+
}
|
|
143
|
+
catch (error) {
|
|
144
|
+
throw (0, base_js_1.streamError)(error, timeout, conn.provider);
|
|
145
|
+
}
|
|
146
|
+
finally {
|
|
147
|
+
timeout.done();
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
listModels(conn) {
|
|
151
|
+
return (0, base_js_1.listOpenModels)(conn, this.profile);
|
|
152
|
+
}
|
|
153
|
+
health(conn) {
|
|
154
|
+
return (0, base_js_1.health)(conn, this.profile);
|
|
155
|
+
}
|
|
156
|
+
async embed(conn, req) {
|
|
157
|
+
const inputs = Array.isArray(req.input) ? req.input : [req.input];
|
|
158
|
+
const { data } = await (0, transport_js_1.fetchJson)((0, util_js_1.joinUrl)(conn.baseUrl, '/embeddings'), {
|
|
159
|
+
method: 'POST',
|
|
160
|
+
headers: { 'content-type': 'application/json', ...conn.headers },
|
|
161
|
+
body: JSON.stringify((0, util_js_1.applyProviderOptions)({ model: req.model, input: inputs }, req.providerOptions)),
|
|
162
|
+
timeoutMs: conn.timeoutMs ?? base_js_1.DEFAULT_TIMEOUT_MS,
|
|
163
|
+
provider: conn.provider,
|
|
164
|
+
});
|
|
165
|
+
const record = (0, util_js_1.asRecord)(data);
|
|
166
|
+
const rows = Array.isArray(record?.data) ? record.data : [];
|
|
167
|
+
// Order by the `index` each row reports, falling back to array order.
|
|
168
|
+
const byIndex = rows
|
|
169
|
+
.map((row, i) => ({ index: (0, util_js_1.asNumber)((0, util_js_1.asRecord)(row)?.index) ?? i, embedding: asEmbedding((0, util_js_1.asRecord)(row)?.embedding) }))
|
|
170
|
+
.sort((a, b) => a.index - b.index);
|
|
171
|
+
const usage = (0, util_js_1.asRecord)(record?.usage);
|
|
172
|
+
const inputTokens = (0, util_js_1.asNumber)(usage?.prompt_tokens);
|
|
173
|
+
return {
|
|
174
|
+
embeddings: byIndex.map((row) => row.embedding),
|
|
175
|
+
model: req.model,
|
|
176
|
+
usage: { inputTokens, outputTokens: 0, estimated: inputTokens === undefined },
|
|
177
|
+
source: { provider: conn.provider, connectionId: conn.id, baseUrl: conn.baseUrl },
|
|
178
|
+
raw: data,
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
exports.OpenAIChatAdapter = OpenAIChatAdapter;
|
|
183
|
+
function asEmbedding(value) {
|
|
184
|
+
return Array.isArray(value) ? value.filter((n) => typeof n === 'number') : [];
|
|
185
|
+
}
|
|
186
|
+
function openAiBody(req, profile) {
|
|
187
|
+
const body = {
|
|
188
|
+
model: req.model,
|
|
189
|
+
messages: req.messages.map(toOpenAiMessage),
|
|
190
|
+
temperature: req.temperature,
|
|
191
|
+
top_p: req.topP,
|
|
192
|
+
stop: req.stopSequences,
|
|
193
|
+
response_format: responseFormatFor(req, profile),
|
|
194
|
+
tools: req.tools?.map((tool) => ({ type: 'function', function: tool })),
|
|
195
|
+
tool_choice: typeof req.toolChoice === 'object' ? { type: 'function', function: { name: req.toolChoice.name } } : req.toolChoice,
|
|
196
|
+
stream: true,
|
|
197
|
+
// Many OpenAI-compatible local servers (llama.cpp, LM Studio, vLLM) choke
|
|
198
|
+
// on or ignore stream_options — only send it where the profile confirms
|
|
199
|
+
// the server understands it.
|
|
200
|
+
stream_options: profile.quirks?.supportsUsageInStream ? { include_usage: true } : undefined,
|
|
201
|
+
};
|
|
202
|
+
if (req.maxTokens !== undefined)
|
|
203
|
+
body[profile.quirks?.maxTokensParam ?? 'max_tokens'] = req.maxTokens;
|
|
204
|
+
// First-class reasoning effort → OpenAI's `reasoning_effort`. Only sent when
|
|
205
|
+
// asked for: non-reasoning models reject the parameter outright.
|
|
206
|
+
if (req.reasoningEffort !== undefined)
|
|
207
|
+
body.reasoning_effort = req.reasoningEffort;
|
|
208
|
+
// providerOptions carries OpenAI-native fields the nugget doesn't model
|
|
209
|
+
// (`reasoning_effort`, `parallel_tool_calls`, `seed`, `logprobs`, …). `apiVersion`
|
|
210
|
+
// is consumed by urlFor for Azure and stripped here so it never hits the body.
|
|
211
|
+
const { apiVersion, ...passthrough } = req.providerOptions ?? {};
|
|
212
|
+
void apiVersion;
|
|
213
|
+
return (0, util_js_1.applyProviderOptions)(body, Object.keys(passthrough).length ? passthrough : undefined);
|
|
214
|
+
}
|
|
215
|
+
function responseFormatFor(req, profile) {
|
|
216
|
+
if (req.responseFormat?.type !== 'json')
|
|
217
|
+
return undefined;
|
|
218
|
+
if (req.responseFormat.schema && profile.quirks?.supportsJsonSchema) {
|
|
219
|
+
return { type: 'json_schema', json_schema: { name: 'response', schema: req.responseFormat.schema } };
|
|
220
|
+
}
|
|
221
|
+
return { type: 'json_object' };
|
|
222
|
+
}
|
|
223
|
+
function toOpenAiMessage(message) {
|
|
224
|
+
return {
|
|
225
|
+
role: message.role,
|
|
226
|
+
content: typeof message.content === 'string'
|
|
227
|
+
? message.content
|
|
228
|
+
: message.content.map((part) => part.type === 'image'
|
|
229
|
+
? { type: 'image_url', image_url: { url: `data:${part.mimeType ?? 'image/png'};base64,${part.imageBase64 ?? ''}` } }
|
|
230
|
+
: { type: 'text', text: part.text ?? '' }),
|
|
231
|
+
name: message.role === 'tool' ? message.name : undefined,
|
|
232
|
+
tool_call_id: message.toolCallId,
|
|
233
|
+
tool_calls: message.toolCalls?.map((call) => ({ id: call.id, type: 'function', function: { name: call.name, arguments: call.raw ?? JSON.stringify(call.arguments) } })),
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
function urlFor(conn, profile, req) {
|
|
237
|
+
if (profile.quirks?.urlTemplate) {
|
|
238
|
+
// `{apiVersion}` (Azure) is filled from a per-call override, then the
|
|
239
|
+
// profile default — so a retired Azure api-version is fixable without a
|
|
240
|
+
// library release: pass `providerOptions: { apiVersion: '2025-01-01-preview' }`.
|
|
241
|
+
const apiVersion = (0, util_js_1.asString)(req.providerOptions?.apiVersion) ?? profile.quirks.apiVersion ?? '';
|
|
242
|
+
return profile.quirks.urlTemplate
|
|
243
|
+
.replace('{baseUrl}', conn.baseUrl)
|
|
244
|
+
.replace('{model}', encodeURIComponent(req.model))
|
|
245
|
+
.replace('{apiVersion}', apiVersion);
|
|
246
|
+
}
|
|
247
|
+
return (0, util_js_1.joinUrl)(conn.baseUrl, '/chat/completions');
|
|
248
|
+
}
|
|
249
|
+
function parseOpenAiResponse(raw) {
|
|
250
|
+
const record = (0, util_js_1.asRecord)(raw);
|
|
251
|
+
const usage = (0, util_js_1.asRecord)(record?.usage);
|
|
252
|
+
const choice = (0, util_js_1.asRecord)(Array.isArray(record?.choices) ? record.choices[0] : undefined);
|
|
253
|
+
const message = (0, util_js_1.asRecord)(choice?.message);
|
|
254
|
+
const text = (0, util_js_1.asString)(message?.content) ?? (0, util_js_1.asString)(record?.text) ?? '';
|
|
255
|
+
const reasoning = (0, util_js_1.asString)(message?.reasoning_content) ?? (0, util_js_1.asString)(message?.reasoning);
|
|
256
|
+
const calls = Array.isArray(message?.tool_calls) ? message.tool_calls : [];
|
|
257
|
+
return {
|
|
258
|
+
text,
|
|
259
|
+
reasoning,
|
|
260
|
+
finish: (0, util_js_1.asString)(choice?.finish_reason),
|
|
261
|
+
toolCalls: calls.map((value) => {
|
|
262
|
+
const call = (0, util_js_1.asRecord)(value);
|
|
263
|
+
const fn = (0, util_js_1.asRecord)(call?.function);
|
|
264
|
+
const rawArgs = (0, util_js_1.asString)(fn?.arguments) ?? '{}';
|
|
265
|
+
return { id: (0, util_js_1.asString)(call?.id) ?? (0, base_js_1.randomId)(), name: (0, util_js_1.asString)(fn?.name) ?? 'unknown', raw: rawArgs, arguments: (0, base_js_1.parseArgs)(rawArgs) };
|
|
266
|
+
}),
|
|
267
|
+
inputTokens: (0, util_js_1.asNumber)(usage?.prompt_tokens),
|
|
268
|
+
outputTokens: (0, util_js_1.asNumber)(usage?.completion_tokens),
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
function makeResult(conn, req, text, toolCalls, finish, started, firstTokenMs, inputTokens, outputTokens) {
|
|
272
|
+
const usage = inputTokens !== undefined || outputTokens !== undefined
|
|
273
|
+
? { inputTokens, outputTokens, estimated: false }
|
|
274
|
+
: (0, tokens_js_1.estimatedUsage)((0, util_js_1.textFromMessages)(req.messages), text);
|
|
275
|
+
return {
|
|
276
|
+
text,
|
|
277
|
+
toolCalls: toolCalls.length ? toolCalls : undefined,
|
|
278
|
+
finishReason: mapFinish(finish, toolCalls.length > 0),
|
|
279
|
+
usage,
|
|
280
|
+
timing: { firstTokenMs, totalMs: Date.now() - started },
|
|
281
|
+
model: req.model,
|
|
282
|
+
source: { provider: conn.provider, connectionId: conn.id, baseUrl: conn.baseUrl },
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
function mapFinish(finish, hasToolCalls) {
|
|
286
|
+
if (hasToolCalls || finish === 'tool_calls' || finish === 'function_call')
|
|
287
|
+
return 'tool';
|
|
288
|
+
if (finish === 'length')
|
|
289
|
+
return 'length';
|
|
290
|
+
if (finish === 'content_filter')
|
|
291
|
+
return 'content_filter';
|
|
292
|
+
return 'stop';
|
|
293
|
+
}
|
|
294
|
+
/**
|
|
295
|
+
* True when a `/chat/completions` 400 is the "function tools with reasoning_effort
|
|
296
|
+
* are not supported" refusal AND the request actually carried tools AND the caller
|
|
297
|
+
* did not already pin `reasoning_effort` (in which case the choice is theirs).
|
|
298
|
+
*/
|
|
299
|
+
function shouldRetryWithoutReasoningEffort(error, req) {
|
|
300
|
+
if (!(error instanceof errors_js_1.AIError) || error.status !== 400)
|
|
301
|
+
return false;
|
|
302
|
+
if (!req.tools || req.tools.length === 0)
|
|
303
|
+
return false;
|
|
304
|
+
// Already `'none'` (from either knob) — the refusal is about something else.
|
|
305
|
+
const effective = req.providerOptions && 'reasoning_effort' in req.providerOptions
|
|
306
|
+
? req.providerOptions.reasoning_effort
|
|
307
|
+
: req.reasoningEffort;
|
|
308
|
+
if (effective === 'none')
|
|
309
|
+
return false;
|
|
310
|
+
const text = `${error.raw ?? ''} ${error.message}`.toLowerCase();
|
|
311
|
+
return text.includes('reasoning_effort') && text.includes('tool');
|
|
312
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.profileFor = exports.PROVIDER_PROFILES = void 0;
|
|
4
|
+
exports.adapterFor = adapterFor;
|
|
5
|
+
const anthropic_js_1 = require("./engines/anthropic.js");
|
|
6
|
+
const google_js_1 = require("./engines/google.js");
|
|
7
|
+
const ollama_js_1 = require("./engines/ollama.js");
|
|
8
|
+
const openaiChat_js_1 = require("./engines/openaiChat.js");
|
|
9
|
+
const profiles_js_1 = require("./profiles.js");
|
|
10
|
+
function adapterFor(provider, baseUrl) {
|
|
11
|
+
const profile = (0, profiles_js_1.profileFor)(provider, baseUrl);
|
|
12
|
+
if (profile.engine === 'anthropic')
|
|
13
|
+
return new anthropic_js_1.AnthropicAdapter(provider);
|
|
14
|
+
if (profile.engine === 'google')
|
|
15
|
+
return new google_js_1.GoogleAdapter(provider);
|
|
16
|
+
if (profile.engine === 'ollama')
|
|
17
|
+
return new ollama_js_1.OllamaAdapter(provider);
|
|
18
|
+
return new openaiChat_js_1.OpenAIChatAdapter(provider, profile);
|
|
19
|
+
}
|
|
20
|
+
var profiles_js_2 = require("./profiles.js");
|
|
21
|
+
Object.defineProperty(exports, "PROVIDER_PROFILES", { enumerable: true, get: function () { return profiles_js_2.PROVIDER_PROFILES; } });
|
|
22
|
+
Object.defineProperty(exports, "profileFor", { enumerable: true, get: function () { return profiles_js_2.profileFor; } });
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.PROVIDER_PROFILES = void 0;
|
|
4
|
+
exports.profileFor = profileFor;
|
|
5
|
+
exports.applyAuth = applyAuth;
|
|
6
|
+
const HOSTED_CLOUD = { nativeTools: true, jsonMode: true, local: false, embeddable: false };
|
|
7
|
+
const LOCAL_RUNTIME = { nativeTools: false, jsonMode: false, local: true, embeddable: true };
|
|
8
|
+
exports.PROVIDER_PROFILES = {
|
|
9
|
+
openai: {
|
|
10
|
+
engine: 'openaiChat',
|
|
11
|
+
defaultBaseUrl: 'https://api.openai.com/v1',
|
|
12
|
+
auth: 'bearer',
|
|
13
|
+
listModelsPath: '/models',
|
|
14
|
+
capabilities: HOSTED_CLOUD,
|
|
15
|
+
quirks: { supportsUsageInStream: true, maxTokensParam: 'max_completion_tokens', supportsJsonSchema: true, supportsIdempotencyKey: true },
|
|
16
|
+
},
|
|
17
|
+
'azure-openai': {
|
|
18
|
+
engine: 'openaiChat',
|
|
19
|
+
auth: 'api-key-header',
|
|
20
|
+
capabilities: HOSTED_CLOUD,
|
|
21
|
+
quirks: { urlTemplate: '{baseUrl}/openai/deployments/{model}/chat/completions?api-version={apiVersion}', apiVersion: '2024-10-21', maxTokensParam: 'max_completion_tokens', supportsJsonSchema: true },
|
|
22
|
+
},
|
|
23
|
+
openrouter: {
|
|
24
|
+
engine: 'openaiChat',
|
|
25
|
+
defaultBaseUrl: 'https://openrouter.ai/api/v1',
|
|
26
|
+
auth: 'bearer',
|
|
27
|
+
defaultHeaders: { 'HTTP-Referer': 'https://github.com/jxburros/AI-Nugget', 'X-Title': 'ai-nugget' },
|
|
28
|
+
listModelsPath: '/models',
|
|
29
|
+
capabilities: HOSTED_CLOUD,
|
|
30
|
+
quirks: { supportsUsageInStream: true },
|
|
31
|
+
},
|
|
32
|
+
groq: { engine: 'openaiChat', defaultBaseUrl: 'https://api.groq.com/openai/v1', auth: 'bearer', listModelsPath: '/models', capabilities: HOSTED_CLOUD },
|
|
33
|
+
deepseek: { engine: 'openaiChat', defaultBaseUrl: 'https://api.deepseek.com/v1', auth: 'bearer', listModelsPath: '/models', capabilities: HOSTED_CLOUD },
|
|
34
|
+
mistral: { engine: 'openaiChat', defaultBaseUrl: 'https://api.mistral.ai/v1', auth: 'bearer', listModelsPath: '/models', capabilities: HOSTED_CLOUD },
|
|
35
|
+
together: { engine: 'openaiChat', defaultBaseUrl: 'https://api.together.xyz/v1', auth: 'bearer', listModelsPath: '/models', capabilities: HOSTED_CLOUD },
|
|
36
|
+
fireworks: { engine: 'openaiChat', defaultBaseUrl: 'https://api.fireworks.ai/inference/v1', auth: 'bearer', listModelsPath: '/models', capabilities: HOSTED_CLOUD },
|
|
37
|
+
cerebras: { engine: 'openaiChat', defaultBaseUrl: 'https://api.cerebras.ai/v1', auth: 'bearer', listModelsPath: '/models', capabilities: HOSTED_CLOUD },
|
|
38
|
+
moonshot: { engine: 'openaiChat', defaultBaseUrl: 'https://api.moonshot.ai/v1', auth: 'bearer', listModelsPath: '/models', capabilities: HOSTED_CLOUD },
|
|
39
|
+
// Cohere's OpenAI-compatible surface lives under /compatibility/v1.
|
|
40
|
+
cohere: { engine: 'openaiChat', defaultBaseUrl: 'https://api.cohere.ai/compatibility/v1', auth: 'bearer', listModelsPath: '/models', capabilities: HOSTED_CLOUD },
|
|
41
|
+
// Perplexity has no public model-listing endpoint, so listModels resolves empty.
|
|
42
|
+
perplexity: { engine: 'openaiChat', defaultBaseUrl: 'https://api.perplexity.ai', auth: 'bearer', capabilities: HOSTED_CLOUD },
|
|
43
|
+
lmstudio: {
|
|
44
|
+
engine: 'openaiChat',
|
|
45
|
+
defaultBaseUrl: 'http://localhost:1234/v1',
|
|
46
|
+
auth: 'none',
|
|
47
|
+
listModelsPath: '/models',
|
|
48
|
+
capabilities: LOCAL_RUNTIME,
|
|
49
|
+
quirks: { keyOptional: true },
|
|
50
|
+
},
|
|
51
|
+
llamacpp: {
|
|
52
|
+
engine: 'openaiChat',
|
|
53
|
+
defaultBaseUrl: 'http://localhost:8080/v1',
|
|
54
|
+
auth: 'none',
|
|
55
|
+
listModelsPath: '/models',
|
|
56
|
+
healthPath: '/health',
|
|
57
|
+
capabilities: LOCAL_RUNTIME,
|
|
58
|
+
quirks: { keyOptional: true, modelOptional: true },
|
|
59
|
+
},
|
|
60
|
+
vllm: {
|
|
61
|
+
engine: 'openaiChat',
|
|
62
|
+
defaultBaseUrl: 'http://localhost:8000/v1',
|
|
63
|
+
auth: 'none',
|
|
64
|
+
listModelsPath: '/models',
|
|
65
|
+
capabilities: LOCAL_RUNTIME,
|
|
66
|
+
quirks: { keyOptional: true },
|
|
67
|
+
},
|
|
68
|
+
ollama: {
|
|
69
|
+
engine: 'ollama',
|
|
70
|
+
defaultBaseUrl: 'http://localhost:11434',
|
|
71
|
+
auth: 'none',
|
|
72
|
+
listModelsPath: '/api/tags',
|
|
73
|
+
// Ollama's `format: 'json'` is engine-enforced (reliable regardless of
|
|
74
|
+
// model); its `tools` field is honored only by models trained for tool
|
|
75
|
+
// use, so nativeTools stays conservative like the other local runtimes.
|
|
76
|
+
capabilities: { ...LOCAL_RUNTIME, jsonMode: true },
|
|
77
|
+
quirks: { keyOptional: true },
|
|
78
|
+
},
|
|
79
|
+
anthropic: {
|
|
80
|
+
engine: 'anthropic',
|
|
81
|
+
defaultBaseUrl: 'https://api.anthropic.com',
|
|
82
|
+
auth: 'x-api-key',
|
|
83
|
+
defaultHeaders: { 'anthropic-version': '2023-06-01' },
|
|
84
|
+
capabilities: HOSTED_CLOUD,
|
|
85
|
+
quirks: { maxTokensRequired: true },
|
|
86
|
+
},
|
|
87
|
+
google: {
|
|
88
|
+
engine: 'google',
|
|
89
|
+
defaultBaseUrl: 'https://generativelanguage.googleapis.com',
|
|
90
|
+
auth: 'x-goog-api-key',
|
|
91
|
+
capabilities: HOSTED_CLOUD,
|
|
92
|
+
},
|
|
93
|
+
'openai-compat': {
|
|
94
|
+
engine: 'openaiChat',
|
|
95
|
+
auth: 'bearer',
|
|
96
|
+
listModelsPath: '/models',
|
|
97
|
+
// Unknown endpoint — usually pointed at a local/self-hosted server, so
|
|
98
|
+
// assume the conservative local-runtime defaults rather than the hosted-cloud ones.
|
|
99
|
+
capabilities: LOCAL_RUNTIME,
|
|
100
|
+
// Most servers reached through this escape hatch (JX Runtime and other
|
|
101
|
+
// local OpenAI-compatible runtimes) don't require a key at all; keep
|
|
102
|
+
// `auth: 'bearer'` for the ones that do (a key is only sent if provided).
|
|
103
|
+
quirks: { keyOptional: true },
|
|
104
|
+
},
|
|
105
|
+
};
|
|
106
|
+
function profileFor(provider, baseUrl) {
|
|
107
|
+
return exports.PROVIDER_PROFILES[provider] ?? {
|
|
108
|
+
...exports.PROVIDER_PROFILES['openai-compat'],
|
|
109
|
+
defaultBaseUrl: baseUrl,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
function applyAuth(profile, apiKey, headers) {
|
|
113
|
+
const next = { ...profile.defaultHeaders, ...headers };
|
|
114
|
+
if (!apiKey)
|
|
115
|
+
return next;
|
|
116
|
+
if (profile.auth === 'bearer')
|
|
117
|
+
next.authorization = `Bearer ${apiKey}`;
|
|
118
|
+
else if (profile.auth === 'x-api-key')
|
|
119
|
+
next['x-api-key'] = apiKey;
|
|
120
|
+
else if (profile.auth === 'x-goog-api-key')
|
|
121
|
+
next['x-goog-api-key'] = apiKey;
|
|
122
|
+
else if (profile.auth === 'api-key-header')
|
|
123
|
+
next['api-key'] = apiKey;
|
|
124
|
+
return next;
|
|
125
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./tools.js"), exports);
|
|
18
|
+
__exportStar(require("./loop.js"), exports);
|