@absolutejs/ai 0.0.41 → 0.0.43
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 +8 -0
- package/dist/ai/index.js +241 -49
- package/dist/ai/index.js.map +6 -5
- package/dist/ai/providers/anthropic.js +3 -2
- package/dist/ai/providers/anthropic.js.map +3 -3
- package/dist/src/ai/index.d.ts +2 -0
- package/dist/src/ai/providerProxy.d.ts +16 -0
- package/dist/types/ai.d.ts +13 -0
- package/dist/types/anthropic.d.ts +2 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -4,3 +4,11 @@ Standalone AI runtime and provider package extracted from AbsoluteJS.
|
|
|
4
4
|
|
|
5
5
|
This package currently focuses on generic AI/chat/provider functionality.
|
|
6
6
|
RAG remains a separate package.
|
|
7
|
+
|
|
8
|
+
Provider traffic can cross a trusted control plane without reimplementing a
|
|
9
|
+
vendor protocol. `remoteProvider()` carries normalized provider parameters and
|
|
10
|
+
chunks over SSE, while `createProviderProxyResponse()` hosts any
|
|
11
|
+
`AIProviderConfig` with pre-first-token and inter-token heartbeats. Provider
|
|
12
|
+
callbacks and abort objects never cross the wire. The Anthropic provider also
|
|
13
|
+
accepts an injectable `fetch`, allowing hosts to retain egress policy, tracing,
|
|
14
|
+
and test transports.
|
package/dist/ai/index.js
CHANGED
|
@@ -1956,7 +1956,8 @@ async function* parseSSEStream4(body, signal) {
|
|
|
1956
1956
|
var fetchAndStream = async function* (baseUrl, config2, params, configuredMax, promptCaching) {
|
|
1957
1957
|
const body = buildRequestBody4(params, configuredMax, promptCaching);
|
|
1958
1958
|
const target = `${baseUrl}/v1/messages`;
|
|
1959
|
-
const
|
|
1959
|
+
const fetchImpl = config2.fetch ?? fetch;
|
|
1960
|
+
const response = await fetchImpl(target, {
|
|
1960
1961
|
...h2IfHttps4(target),
|
|
1961
1962
|
body: JSON.stringify(body),
|
|
1962
1963
|
headers: {
|
|
@@ -2950,57 +2951,86 @@ var streamTurns = async function* (options, renderers, messages, signal, startTi
|
|
|
2950
2951
|
turn: 0
|
|
2951
2952
|
};
|
|
2952
2953
|
const toolDefs = options.tools ? buildToolDefinitions2(options.tools) : undefined;
|
|
2953
|
-
|
|
2954
|
-
|
|
2955
|
-
|
|
2956
|
-
|
|
2957
|
-
|
|
2958
|
-
|
|
2959
|
-
|
|
2960
|
-
|
|
2961
|
-
|
|
2962
|
-
|
|
2963
|
-
|
|
2964
|
-
cacheSystemPrompt: options.cacheSystemPrompt,
|
|
2965
|
-
maxTokens: options.maxTokens,
|
|
2966
|
-
messages: turnState.currentMessages,
|
|
2967
|
-
model: options.model,
|
|
2968
|
-
promptCaching: options.promptCaching,
|
|
2969
|
-
reasoning: options.reasoning,
|
|
2970
|
-
signal,
|
|
2971
|
-
systemPrompt: options.systemPrompt,
|
|
2972
|
-
tools: toolDefs
|
|
2973
|
-
});
|
|
2974
|
-
yield* consumeStream2(stream, chunkState, renderers, options, turnState, signal);
|
|
2975
|
-
options.onTurn?.(turnState.turn, chunkState.usage, turnState.fullResponse.slice(responseBeforeTurn.length));
|
|
2976
|
-
runningTotalTokens += (chunkState.usage?.inputTokens ?? 0) + (chunkState.usage?.outputTokens ?? 0);
|
|
2977
|
-
if (chunkState.stopReason === "max_tokens") {
|
|
2978
|
-
yield {
|
|
2979
|
-
data: renderers.error(`Response truncated at max_tokens (output=${chunkState.usage?.outputTokens ?? "?"}). ` + `Raise maxTokens on the provider/options, split the request, or reduce upstream context.`),
|
|
2980
|
-
event: "status"
|
|
2981
|
-
};
|
|
2982
|
-
return;
|
|
2983
|
-
}
|
|
2984
|
-
if (options.maxTotalTokens && runningTotalTokens >= options.maxTotalTokens) {
|
|
2985
|
-
yield {
|
|
2986
|
-
data: renderers.error(`Stopped: token budget reached (${runningTotalTokens}/${options.maxTotalTokens} tokens over ` + `${turnState.turn} turns). Narrow the request or raise maxTotalTokens.`),
|
|
2987
|
-
event: "status"
|
|
2988
|
-
};
|
|
2989
|
-
return;
|
|
2990
|
-
}
|
|
2991
|
-
if (options.maxDurationMs && Date.now() - startTime >= options.maxDurationMs) {
|
|
2992
|
-
yield {
|
|
2993
|
-
data: renderers.error(`Stopped: time budget reached (${Math.round((Date.now() - startTime) / 1000)}s over ` + `${turnState.turn} turns). Narrow the request or raise maxDurationMs.`),
|
|
2994
|
-
event: "status"
|
|
2954
|
+
const aggregateUsage = { inputTokens: 0, outputTokens: 0 };
|
|
2955
|
+
let finishReason = "max_turns";
|
|
2956
|
+
let completedTurns = 0;
|
|
2957
|
+
try {
|
|
2958
|
+
for (;turnState.turn < maxTurns && !signal.aborted; turnState.turn++) {
|
|
2959
|
+
const chunkState = {
|
|
2960
|
+
contentBlocks: [],
|
|
2961
|
+
currentThinking: null,
|
|
2962
|
+
pendingToolCalls: [],
|
|
2963
|
+
stopReason: undefined,
|
|
2964
|
+
usage: undefined
|
|
2995
2965
|
};
|
|
2996
|
-
|
|
2966
|
+
const responseBeforeTurn = turnState.fullResponse;
|
|
2967
|
+
const stream = options.provider.stream({
|
|
2968
|
+
cacheSystemPrompt: options.cacheSystemPrompt,
|
|
2969
|
+
maxTokens: options.maxTokens,
|
|
2970
|
+
messages: turnState.currentMessages,
|
|
2971
|
+
model: options.model,
|
|
2972
|
+
promptCaching: options.promptCaching,
|
|
2973
|
+
reasoning: options.reasoning,
|
|
2974
|
+
signal,
|
|
2975
|
+
systemPrompt: options.systemPrompt,
|
|
2976
|
+
tools: toolDefs
|
|
2977
|
+
});
|
|
2978
|
+
yield* consumeStream2(stream, chunkState, renderers, options, turnState, signal);
|
|
2979
|
+
options.onTurn?.(turnState.turn, chunkState.usage, turnState.fullResponse.slice(responseBeforeTurn.length));
|
|
2980
|
+
completedTurns += 1;
|
|
2981
|
+
aggregateUsage.inputTokens += chunkState.usage?.inputTokens ?? 0;
|
|
2982
|
+
aggregateUsage.outputTokens += chunkState.usage?.outputTokens ?? 0;
|
|
2983
|
+
aggregateUsage.cacheReadInputTokens = (aggregateUsage.cacheReadInputTokens ?? 0) + (chunkState.usage?.cacheReadInputTokens ?? 0);
|
|
2984
|
+
aggregateUsage.cacheWriteInputTokens = (aggregateUsage.cacheWriteInputTokens ?? 0) + (chunkState.usage?.cacheWriteInputTokens ?? 0);
|
|
2985
|
+
const runningTotalTokens = aggregateUsage.inputTokens + aggregateUsage.outputTokens;
|
|
2986
|
+
if (chunkState.stopReason === "max_tokens") {
|
|
2987
|
+
finishReason = "max_tokens";
|
|
2988
|
+
yield {
|
|
2989
|
+
data: renderers.error(`Response truncated at max_tokens (output=${chunkState.usage?.outputTokens ?? "?"}). ` + `Raise maxTokens on the provider/options, split the request, or reduce upstream context.`),
|
|
2990
|
+
event: "status"
|
|
2991
|
+
};
|
|
2992
|
+
return;
|
|
2993
|
+
}
|
|
2994
|
+
if (options.maxTotalTokens && runningTotalTokens >= options.maxTotalTokens) {
|
|
2995
|
+
finishReason = "max_total_tokens";
|
|
2996
|
+
yield {
|
|
2997
|
+
data: renderers.error(`Stopped: token budget reached (${runningTotalTokens}/${options.maxTotalTokens} tokens over ` + `${turnState.turn} turns). Narrow the request or raise maxTotalTokens.`),
|
|
2998
|
+
event: "status"
|
|
2999
|
+
};
|
|
3000
|
+
return;
|
|
3001
|
+
}
|
|
3002
|
+
if (options.maxDurationMs && Date.now() - startTime >= options.maxDurationMs) {
|
|
3003
|
+
finishReason = "max_duration";
|
|
3004
|
+
yield {
|
|
3005
|
+
data: renderers.error(`Stopped: time budget reached (${Math.round((Date.now() - startTime) / 1000)}s over ` + `${turnState.turn} turns). Narrow the request or raise maxDurationMs.`),
|
|
3006
|
+
event: "status"
|
|
3007
|
+
};
|
|
3008
|
+
return;
|
|
3009
|
+
}
|
|
3010
|
+
if (shouldStopToolLoop(chunkState, turnState, signal)) {
|
|
3011
|
+
finishReason = signal.aborted ? "aborted" : "complete";
|
|
3012
|
+
return void (yield yieldCompletion(renderers, options, turnState.fullResponse, chunkState.usage, startTime));
|
|
3013
|
+
}
|
|
3014
|
+
yield* processTurn(chunkState, options, renderers, turnState);
|
|
2997
3015
|
}
|
|
2998
|
-
if (
|
|
2999
|
-
|
|
3016
|
+
if (signal.aborted)
|
|
3017
|
+
finishReason = "aborted";
|
|
3018
|
+
} catch (error) {
|
|
3019
|
+
finishReason = signal.aborted ? "aborted" : "error";
|
|
3020
|
+
throw error;
|
|
3021
|
+
} finally {
|
|
3022
|
+
try {
|
|
3023
|
+
await options.onFinish?.({
|
|
3024
|
+
durationMs: Date.now() - startTime,
|
|
3025
|
+
fullResponse: turnState.fullResponse,
|
|
3026
|
+
reason: finishReason,
|
|
3027
|
+
turns: completedTurns,
|
|
3028
|
+
usage: aggregateUsage
|
|
3029
|
+
});
|
|
3030
|
+
} catch (error) {
|
|
3031
|
+
console.error("[absolute-ai] onFinish rejected:", error);
|
|
3000
3032
|
}
|
|
3001
|
-
yield* processTurn(chunkState, options, renderers, turnState);
|
|
3002
3033
|
}
|
|
3003
|
-
return;
|
|
3004
3034
|
};
|
|
3005
3035
|
|
|
3006
3036
|
// src/constants.ts
|
|
@@ -3654,6 +3684,165 @@ var streamAIWithTools = async function* (options) {
|
|
|
3654
3684
|
yield { ...summary, type: "done" };
|
|
3655
3685
|
return summary;
|
|
3656
3686
|
};
|
|
3687
|
+
// src/ai/providerProxy.ts
|
|
3688
|
+
var DEFAULT_HEARTBEAT_MS2 = 5000;
|
|
3689
|
+
var encoder = new TextEncoder;
|
|
3690
|
+
var wireParams = (params) => ({
|
|
3691
|
+
...params.cacheSystemPrompt === undefined ? {} : { cacheSystemPrompt: params.cacheSystemPrompt },
|
|
3692
|
+
...params.frequencyPenalty === undefined ? {} : { frequencyPenalty: params.frequencyPenalty },
|
|
3693
|
+
...params.maxTokens === undefined ? {} : { maxTokens: params.maxTokens },
|
|
3694
|
+
messages: params.messages,
|
|
3695
|
+
model: params.model,
|
|
3696
|
+
...params.parallelToolCalls === undefined ? {} : { parallelToolCalls: params.parallelToolCalls },
|
|
3697
|
+
...params.presencePenalty === undefined ? {} : { presencePenalty: params.presencePenalty },
|
|
3698
|
+
...params.promptCaching === undefined ? {} : { promptCaching: params.promptCaching },
|
|
3699
|
+
...params.reasoning === undefined ? {} : { reasoning: params.reasoning },
|
|
3700
|
+
...params.responseFormat === undefined ? {} : { responseFormat: params.responseFormat },
|
|
3701
|
+
...params.seed === undefined ? {} : { seed: params.seed },
|
|
3702
|
+
...params.stopSequences === undefined ? {} : { stopSequences: params.stopSequences },
|
|
3703
|
+
...params.systemPrompt === undefined ? {} : { systemPrompt: params.systemPrompt },
|
|
3704
|
+
...params.temperature === undefined ? {} : { temperature: params.temperature },
|
|
3705
|
+
...params.toolChoice === undefined ? {} : { toolChoice: params.toolChoice },
|
|
3706
|
+
...params.tools === undefined ? {} : { tools: params.tools },
|
|
3707
|
+
...params.topP === undefined ? {} : { topP: params.topP }
|
|
3708
|
+
});
|
|
3709
|
+
var parseProviderProxyParams = (value) => {
|
|
3710
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
3711
|
+
return null;
|
|
3712
|
+
const input = value;
|
|
3713
|
+
if (typeof input.model !== "string" || input.model.trim() === "")
|
|
3714
|
+
return null;
|
|
3715
|
+
if (!Array.isArray(input.messages))
|
|
3716
|
+
return null;
|
|
3717
|
+
return wireParams(input);
|
|
3718
|
+
};
|
|
3719
|
+
var encodeEvent = (event, data) => encoder.encode(`event: ${event}
|
|
3720
|
+
data: ${JSON.stringify(data)}
|
|
3721
|
+
|
|
3722
|
+
`);
|
|
3723
|
+
var errorPayload = (error) => {
|
|
3724
|
+
const providerError = error instanceof ProviderError ? error : ProviderError.from(error, "remote");
|
|
3725
|
+
return {
|
|
3726
|
+
message: providerError.message,
|
|
3727
|
+
provider: providerError.provider,
|
|
3728
|
+
retryable: providerError.retryable,
|
|
3729
|
+
status: providerError.status,
|
|
3730
|
+
type: providerError.type
|
|
3731
|
+
};
|
|
3732
|
+
};
|
|
3733
|
+
var streamResponseBody = (iterator, heartbeatMs, onError) => new ReadableStream({
|
|
3734
|
+
async start(controller) {
|
|
3735
|
+
try {
|
|
3736
|
+
for (;; ) {
|
|
3737
|
+
const pending = iterator.next();
|
|
3738
|
+
let next;
|
|
3739
|
+
for (;; ) {
|
|
3740
|
+
let timer;
|
|
3741
|
+
const heartbeat = new Promise((resolve) => {
|
|
3742
|
+
timer = setTimeout(() => resolve("heartbeat"), heartbeatMs);
|
|
3743
|
+
});
|
|
3744
|
+
const winner = heartbeatMs > 0 ? await Promise.race([pending, heartbeat]) : await pending;
|
|
3745
|
+
if (timer)
|
|
3746
|
+
clearTimeout(timer);
|
|
3747
|
+
if (winner === "heartbeat") {
|
|
3748
|
+
controller.enqueue(encoder.encode(`: ping
|
|
3749
|
+
|
|
3750
|
+
`));
|
|
3751
|
+
continue;
|
|
3752
|
+
}
|
|
3753
|
+
next = winner;
|
|
3754
|
+
break;
|
|
3755
|
+
}
|
|
3756
|
+
if (next.done)
|
|
3757
|
+
break;
|
|
3758
|
+
controller.enqueue(encodeEvent("chunk", next.value));
|
|
3759
|
+
}
|
|
3760
|
+
} catch (error) {
|
|
3761
|
+
await onError?.(error);
|
|
3762
|
+
controller.enqueue(encodeEvent("error", errorPayload(error)));
|
|
3763
|
+
} finally {
|
|
3764
|
+
await iterator.return?.();
|
|
3765
|
+
controller.close();
|
|
3766
|
+
}
|
|
3767
|
+
}
|
|
3768
|
+
});
|
|
3769
|
+
var createProviderProxyResponse = async (provider, value, options = {}) => {
|
|
3770
|
+
const params = parseProviderProxyParams(value);
|
|
3771
|
+
if (!params) {
|
|
3772
|
+
return Response.json({ error: "invalid provider stream request" }, { status: 400 });
|
|
3773
|
+
}
|
|
3774
|
+
const iterator = provider.stream({ ...params, signal: options.signal })[Symbol.asyncIterator]();
|
|
3775
|
+
return new Response(streamResponseBody(iterator, options.heartbeatMs ?? DEFAULT_HEARTBEAT_MS2, options.onError), {
|
|
3776
|
+
headers: {
|
|
3777
|
+
"cache-control": "no-cache",
|
|
3778
|
+
"content-type": "text/event-stream; charset=utf-8",
|
|
3779
|
+
"x-accel-buffering": "no",
|
|
3780
|
+
...Object.fromEntries(new Headers(options.headers))
|
|
3781
|
+
}
|
|
3782
|
+
});
|
|
3783
|
+
};
|
|
3784
|
+
var parseRemoteStream = async function* (response) {
|
|
3785
|
+
if (!response.ok) {
|
|
3786
|
+
throw ProviderError.fromResponse("remote", response.status, await response.text());
|
|
3787
|
+
}
|
|
3788
|
+
if (!response.body)
|
|
3789
|
+
throw new ProviderError({
|
|
3790
|
+
message: "Remote provider returned no response body",
|
|
3791
|
+
provider: "remote",
|
|
3792
|
+
retryable: true
|
|
3793
|
+
});
|
|
3794
|
+
const reader = response.body.getReader();
|
|
3795
|
+
const decoder = new TextDecoder;
|
|
3796
|
+
let buffer = "";
|
|
3797
|
+
try {
|
|
3798
|
+
for (;; ) {
|
|
3799
|
+
const { done, value } = await reader.read();
|
|
3800
|
+
buffer += decoder.decode(value, { stream: !done });
|
|
3801
|
+
const frames = buffer.split(/\r?\n\r?\n/);
|
|
3802
|
+
buffer = frames.pop() ?? "";
|
|
3803
|
+
for (const frame of frames) {
|
|
3804
|
+
if (frame.startsWith(":"))
|
|
3805
|
+
continue;
|
|
3806
|
+
const event = /^event:\s*(.+)$/m.exec(frame)?.[1];
|
|
3807
|
+
const data = frame.split(/\r?\n/).filter((line) => line.startsWith("data:")).map((line) => line.slice(5).trimStart()).join(`
|
|
3808
|
+
`);
|
|
3809
|
+
if (!data)
|
|
3810
|
+
continue;
|
|
3811
|
+
const parsed = JSON.parse(data);
|
|
3812
|
+
if (event === "error") {
|
|
3813
|
+
throw new ProviderError({
|
|
3814
|
+
message: "message" in parsed && typeof parsed.message === "string" ? parsed.message : "Remote provider stream failed",
|
|
3815
|
+
provider: "provider" in parsed && typeof parsed.provider === "string" ? parsed.provider : "remote",
|
|
3816
|
+
retryable: "retryable" in parsed && typeof parsed.retryable === "boolean" ? parsed.retryable : true,
|
|
3817
|
+
status: "status" in parsed && (typeof parsed.status === "number" || parsed.status === null) ? parsed.status : null,
|
|
3818
|
+
type: "type" in parsed && (typeof parsed.type === "string" || parsed.type === null) ? parsed.type : null
|
|
3819
|
+
});
|
|
3820
|
+
}
|
|
3821
|
+
if (event === "chunk")
|
|
3822
|
+
yield parsed;
|
|
3823
|
+
}
|
|
3824
|
+
if (done)
|
|
3825
|
+
break;
|
|
3826
|
+
}
|
|
3827
|
+
} finally {
|
|
3828
|
+
reader.releaseLock();
|
|
3829
|
+
}
|
|
3830
|
+
};
|
|
3831
|
+
var remoteProvider = (config2) => ({
|
|
3832
|
+
stream: async function* (params) {
|
|
3833
|
+
const headers = typeof config2.headers === "function" ? await config2.headers() : config2.headers;
|
|
3834
|
+
const response = await (config2.fetch ?? fetch)(config2.url, {
|
|
3835
|
+
body: JSON.stringify(wireParams(params)),
|
|
3836
|
+
headers: {
|
|
3837
|
+
"content-type": "application/json",
|
|
3838
|
+
...Object.fromEntries(new Headers(headers))
|
|
3839
|
+
},
|
|
3840
|
+
method: "POST",
|
|
3841
|
+
signal: params.signal
|
|
3842
|
+
});
|
|
3843
|
+
yield* parseRemoteStream(response);
|
|
3844
|
+
}
|
|
3845
|
+
});
|
|
3657
3846
|
// src/ai/ui/uiCards.ts
|
|
3658
3847
|
var createUiCards = (definitions) => {
|
|
3659
3848
|
const byName = new Map(definitions.map((definition) => [definition.name, definition]));
|
|
@@ -5680,11 +5869,13 @@ export {
|
|
|
5680
5869
|
serializeAIMessage,
|
|
5681
5870
|
resolveRenderers,
|
|
5682
5871
|
renderChartSvg,
|
|
5872
|
+
remoteProvider,
|
|
5683
5873
|
providerStatusPage,
|
|
5684
5874
|
planCard,
|
|
5685
5875
|
parseUiActions,
|
|
5686
5876
|
parseTableSpec,
|
|
5687
5877
|
parseStatTilesSpec,
|
|
5878
|
+
parseProviderProxyParams,
|
|
5688
5879
|
parsePlanSpec,
|
|
5689
5880
|
parseFormSpec,
|
|
5690
5881
|
parseDiffSpec,
|
|
@@ -5714,6 +5905,7 @@ export {
|
|
|
5714
5905
|
credentialCard,
|
|
5715
5906
|
createUiCards,
|
|
5716
5907
|
createSyncConversationStore,
|
|
5908
|
+
createProviderProxyResponse,
|
|
5717
5909
|
createOAuth2ClientCredentialsTokenSource,
|
|
5718
5910
|
createMemoryStore,
|
|
5719
5911
|
createConversationManager,
|
|
@@ -5751,5 +5943,5 @@ export {
|
|
|
5751
5943
|
BUILTIN_UI_CARDS
|
|
5752
5944
|
};
|
|
5753
5945
|
|
|
5754
|
-
//# debugId=
|
|
5946
|
+
//# debugId=DACDBFA8148E663564756E2164756E21
|
|
5755
5947
|
//# sourceMappingURL=index.js.map
|