@guidekit/core 0.1.0-beta.2 → 0.1.0-beta.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.
- package/README.md +1 -1
- package/dist/index.cjs +10 -426
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -126
- package/dist/index.d.ts +2 -126
- package/dist/index.js +11 -426
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -63,7 +63,7 @@ All errors extend `GuideKitError` with structured metadata:
|
|
|
63
63
|
|
|
64
64
|
- **DOM Scanner** — TreeWalker-based page model with `data-guidekit-ignore` support
|
|
65
65
|
- **Context Manager** — Token budgeting and truncation for LLM context windows
|
|
66
|
-
- **LLM Orchestrator** — Streaming responses with tool calling (Gemini
|
|
66
|
+
- **LLM Orchestrator** — Streaming responses with tool calling (Gemini (default), custom adapters via `LLMProviderAdapter`)
|
|
67
67
|
- **Resource Manager** — AbortController pattern and lifecycle tracking
|
|
68
68
|
|
|
69
69
|
## Documentation
|
package/dist/index.cjs
CHANGED
|
@@ -1094,7 +1094,7 @@ var DOMScanner = class {
|
|
|
1094
1094
|
const style = window.getComputedStyle(el);
|
|
1095
1095
|
const position = style.position;
|
|
1096
1096
|
const zIndex = parseInt(style.zIndex, 10) || 0;
|
|
1097
|
-
if ((position === "fixed" || position === "absolute") && !isNaN(zIndex) && zIndex >= 1e3) {
|
|
1097
|
+
if ((position === "fixed" || position === "absolute") && !Number.isNaN(zIndex) && zIndex >= 1e3) {
|
|
1098
1098
|
const visible = isElementVisible(el);
|
|
1099
1099
|
if (!visible) return;
|
|
1100
1100
|
const overlayType = this.classifyOverlay(el, style);
|
|
@@ -1122,10 +1122,10 @@ var DOMScanner = class {
|
|
|
1122
1122
|
return "dropdown";
|
|
1123
1123
|
const width = parseFloat(style.width);
|
|
1124
1124
|
const height = parseFloat(style.height);
|
|
1125
|
-
if (typeof window !== "undefined" && !isNaN(width) && !isNaN(height) && width > window.innerWidth * 0.5 && height > window.innerHeight * 0.5) {
|
|
1125
|
+
if (typeof window !== "undefined" && !Number.isNaN(width) && !Number.isNaN(height) && width > window.innerWidth * 0.5 && height > window.innerHeight * 0.5) {
|
|
1126
1126
|
return "modal";
|
|
1127
1127
|
}
|
|
1128
|
-
if (!isNaN(width) && width < 400) return "popover";
|
|
1128
|
+
if (!Number.isNaN(width) && width < 400) return "popover";
|
|
1129
1129
|
return null;
|
|
1130
1130
|
}
|
|
1131
1131
|
// -------------------------------------------------------------------------
|
|
@@ -1841,424 +1841,9 @@ function isGuideKitError(error) {
|
|
|
1841
1841
|
return error instanceof GuideKitError;
|
|
1842
1842
|
}
|
|
1843
1843
|
|
|
1844
|
-
// src/llm/openai-adapter.ts
|
|
1845
|
-
var DEFAULT_OPENAI_MODEL = "gpt-4o";
|
|
1846
|
-
var DEFAULT_TIMEOUT_MS = 15e3;
|
|
1847
|
-
var OPENAI_CHAT_URL = "https://api.openai.com/v1/chat/completions";
|
|
1848
|
-
function emptyUsage() {
|
|
1849
|
-
return { prompt: 0, completion: 0, total: 0 };
|
|
1850
|
-
}
|
|
1851
|
-
var OpenAIAdapter = class {
|
|
1852
|
-
apiKey;
|
|
1853
|
-
model;
|
|
1854
|
-
/** Tracks whether the last extractChunks call emitted a done chunk. */
|
|
1855
|
-
lastExtractEmittedDone = false;
|
|
1856
|
-
/**
|
|
1857
|
-
* Token usage extracted from the most recent `parseResponse` call.
|
|
1858
|
-
* Updated as each SSE chunk is parsed.
|
|
1859
|
-
*/
|
|
1860
|
-
_lastUsage = emptyUsage();
|
|
1861
|
-
constructor(config) {
|
|
1862
|
-
this.apiKey = config.apiKey;
|
|
1863
|
-
this.model = config.model ?? DEFAULT_OPENAI_MODEL;
|
|
1864
|
-
}
|
|
1865
|
-
/** Token usage from the most recent parseResponse call. */
|
|
1866
|
-
get lastUsage() {
|
|
1867
|
-
return this._lastUsage;
|
|
1868
|
-
}
|
|
1869
|
-
// -----------------------------------------------------------------------
|
|
1870
|
-
// LLMProviderAdapter implementation
|
|
1871
|
-
// -----------------------------------------------------------------------
|
|
1872
|
-
/**
|
|
1873
|
-
* Convert GuideKit tool definitions into OpenAI's `tools` format.
|
|
1874
|
-
* Each tool is wrapped as `{ type: 'function', function: { name, description, parameters } }`.
|
|
1875
|
-
*/
|
|
1876
|
-
formatTools(tools) {
|
|
1877
|
-
if (tools.length === 0) return void 0;
|
|
1878
|
-
return tools.map((tool) => ({
|
|
1879
|
-
type: "function",
|
|
1880
|
-
function: {
|
|
1881
|
-
name: tool.name,
|
|
1882
|
-
description: tool.description,
|
|
1883
|
-
parameters: {
|
|
1884
|
-
type: "object",
|
|
1885
|
-
properties: { ...tool.parameters },
|
|
1886
|
-
required: tool.required ?? []
|
|
1887
|
-
}
|
|
1888
|
-
}
|
|
1889
|
-
}));
|
|
1890
|
-
}
|
|
1891
|
-
/**
|
|
1892
|
-
* Convert an array of `ConversationTurn` objects into OpenAI's messages
|
|
1893
|
-
* format with `role: 'user' | 'assistant'`.
|
|
1894
|
-
*/
|
|
1895
|
-
formatConversation(history) {
|
|
1896
|
-
return history.map((turn) => ({
|
|
1897
|
-
role: turn.role,
|
|
1898
|
-
content: turn.content
|
|
1899
|
-
}));
|
|
1900
|
-
}
|
|
1901
|
-
/**
|
|
1902
|
-
* Parse an OpenAI SSE streaming response into an async iterable of
|
|
1903
|
-
* `TextChunk` and `ToolCall` objects.
|
|
1904
|
-
*
|
|
1905
|
-
* The OpenAI streaming endpoint sends each chunk as a JSON object
|
|
1906
|
-
* prefixed by `data: `. The final line is `data: [DONE]`.
|
|
1907
|
-
* Text content arrives in `choices[0].delta.content` and tool calls
|
|
1908
|
-
* arrive in `choices[0].delta.tool_calls`.
|
|
1909
|
-
*
|
|
1910
|
-
* This method also:
|
|
1911
|
-
* - Detects content filtering and throws `ContentFilterError`.
|
|
1912
|
-
* - Tracks token usage (accessible via `lastUsage` after iteration).
|
|
1913
|
-
*/
|
|
1914
|
-
async *parseResponse(stream) {
|
|
1915
|
-
const reader = stream.getReader();
|
|
1916
|
-
const decoder = new TextDecoder();
|
|
1917
|
-
let buffer = "";
|
|
1918
|
-
let doneEmitted = false;
|
|
1919
|
-
this._lastUsage = emptyUsage();
|
|
1920
|
-
const pendingToolCalls = /* @__PURE__ */ new Map();
|
|
1921
|
-
try {
|
|
1922
|
-
while (true) {
|
|
1923
|
-
const { done, value } = await reader.read();
|
|
1924
|
-
if (done) break;
|
|
1925
|
-
buffer += decoder.decode(value, { stream: true });
|
|
1926
|
-
const lines = buffer.split("\n");
|
|
1927
|
-
buffer = lines.pop() ?? "";
|
|
1928
|
-
for (const line of lines) {
|
|
1929
|
-
const trimmed = line.trim();
|
|
1930
|
-
if (!trimmed.startsWith("data:")) continue;
|
|
1931
|
-
const jsonStr = trimmed.slice(5).trim();
|
|
1932
|
-
if (jsonStr === "" || jsonStr === "[DONE]") {
|
|
1933
|
-
if (jsonStr === "[DONE]") {
|
|
1934
|
-
yield* this.flushPendingToolCalls(pendingToolCalls);
|
|
1935
|
-
if (!doneEmitted) {
|
|
1936
|
-
doneEmitted = true;
|
|
1937
|
-
yield { text: "", done: true };
|
|
1938
|
-
}
|
|
1939
|
-
}
|
|
1940
|
-
continue;
|
|
1941
|
-
}
|
|
1942
|
-
let parsed;
|
|
1943
|
-
try {
|
|
1944
|
-
parsed = JSON.parse(jsonStr);
|
|
1945
|
-
} catch {
|
|
1946
|
-
continue;
|
|
1947
|
-
}
|
|
1948
|
-
if (this.isContentFiltered(parsed)) {
|
|
1949
|
-
throw new ContentFilterError({
|
|
1950
|
-
code: ErrorCodes.CONTENT_FILTER_TRIGGERED,
|
|
1951
|
-
message: "Response was blocked by provider content safety filter.",
|
|
1952
|
-
provider: "openai",
|
|
1953
|
-
suggestion: "Rephrase your question or adjust safety settings."
|
|
1954
|
-
});
|
|
1955
|
-
}
|
|
1956
|
-
const chunkUsage = this.extractUsage(parsed);
|
|
1957
|
-
if (chunkUsage) {
|
|
1958
|
-
this._lastUsage = chunkUsage;
|
|
1959
|
-
}
|
|
1960
|
-
yield* this.extractChunks(parsed, pendingToolCalls, doneEmitted);
|
|
1961
|
-
if (!doneEmitted && this.lastExtractEmittedDone) {
|
|
1962
|
-
doneEmitted = true;
|
|
1963
|
-
}
|
|
1964
|
-
}
|
|
1965
|
-
}
|
|
1966
|
-
if (buffer.trim().startsWith("data:")) {
|
|
1967
|
-
const jsonStr = buffer.trim().slice(5).trim();
|
|
1968
|
-
if (jsonStr === "[DONE]") {
|
|
1969
|
-
yield* this.flushPendingToolCalls(pendingToolCalls);
|
|
1970
|
-
if (!doneEmitted) {
|
|
1971
|
-
doneEmitted = true;
|
|
1972
|
-
yield { text: "", done: true };
|
|
1973
|
-
}
|
|
1974
|
-
} else if (jsonStr !== "") {
|
|
1975
|
-
try {
|
|
1976
|
-
const parsed = JSON.parse(jsonStr);
|
|
1977
|
-
if (this.isContentFiltered(parsed)) {
|
|
1978
|
-
throw new ContentFilterError({
|
|
1979
|
-
code: ErrorCodes.CONTENT_FILTER_TRIGGERED,
|
|
1980
|
-
message: "Response was blocked by provider content safety filter.",
|
|
1981
|
-
provider: "openai",
|
|
1982
|
-
suggestion: "Rephrase your question or adjust safety settings."
|
|
1983
|
-
});
|
|
1984
|
-
}
|
|
1985
|
-
const chunkUsage = this.extractUsage(parsed);
|
|
1986
|
-
if (chunkUsage) {
|
|
1987
|
-
this._lastUsage = chunkUsage;
|
|
1988
|
-
}
|
|
1989
|
-
yield* this.extractChunks(parsed, pendingToolCalls, doneEmitted);
|
|
1990
|
-
if (!doneEmitted && this.lastExtractEmittedDone) {
|
|
1991
|
-
doneEmitted = true;
|
|
1992
|
-
}
|
|
1993
|
-
} catch (error) {
|
|
1994
|
-
if (error instanceof ContentFilterError) throw error;
|
|
1995
|
-
}
|
|
1996
|
-
}
|
|
1997
|
-
}
|
|
1998
|
-
yield* this.flushPendingToolCalls(pendingToolCalls);
|
|
1999
|
-
} finally {
|
|
2000
|
-
reader.releaseLock();
|
|
2001
|
-
}
|
|
2002
|
-
}
|
|
2003
|
-
/**
|
|
2004
|
-
* Format a tool result so it can be sent back to OpenAI as a
|
|
2005
|
-
* `tool` role message with the `tool_call_id`.
|
|
2006
|
-
*/
|
|
2007
|
-
formatToolResult(callId, result) {
|
|
2008
|
-
return {
|
|
2009
|
-
role: "tool",
|
|
2010
|
-
tool_call_id: callId,
|
|
2011
|
-
content: typeof result === "string" ? result : JSON.stringify(result)
|
|
2012
|
-
};
|
|
2013
|
-
}
|
|
2014
|
-
// -----------------------------------------------------------------------
|
|
2015
|
-
// Streaming request
|
|
2016
|
-
// -----------------------------------------------------------------------
|
|
2017
|
-
/**
|
|
2018
|
-
* Build and execute a streaming request to the OpenAI Chat Completions API.
|
|
2019
|
-
* Returns the raw `ReadableStream` for the response body together with
|
|
2020
|
-
* the raw Response object.
|
|
2021
|
-
*/
|
|
2022
|
-
async streamRequest(params) {
|
|
2023
|
-
const contentsArray = params.contents;
|
|
2024
|
-
const messages = [
|
|
2025
|
-
{ role: "system", content: params.systemPrompt },
|
|
2026
|
-
...contentsArray
|
|
2027
|
-
];
|
|
2028
|
-
if (params.userMessage) {
|
|
2029
|
-
messages.push({ role: "user", content: params.userMessage });
|
|
2030
|
-
}
|
|
2031
|
-
const body = {
|
|
2032
|
-
model: this.model,
|
|
2033
|
-
messages,
|
|
2034
|
-
stream: true,
|
|
2035
|
-
temperature: 0.7,
|
|
2036
|
-
top_p: 0.95
|
|
2037
|
-
};
|
|
2038
|
-
if (params.tools) {
|
|
2039
|
-
body.tools = params.tools;
|
|
2040
|
-
}
|
|
2041
|
-
const timeoutMs = params.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
2042
|
-
const controller = new AbortController();
|
|
2043
|
-
const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
|
|
2044
|
-
if (params.signal) {
|
|
2045
|
-
params.signal.addEventListener(
|
|
2046
|
-
"abort",
|
|
2047
|
-
() => controller.abort(params.signal.reason),
|
|
2048
|
-
{ once: true }
|
|
2049
|
-
);
|
|
2050
|
-
}
|
|
2051
|
-
let response;
|
|
2052
|
-
try {
|
|
2053
|
-
response = await fetch(OPENAI_CHAT_URL, {
|
|
2054
|
-
method: "POST",
|
|
2055
|
-
headers: {
|
|
2056
|
-
"Content-Type": "application/json",
|
|
2057
|
-
Authorization: `Bearer ${this.apiKey}`
|
|
2058
|
-
},
|
|
2059
|
-
body: JSON.stringify(body),
|
|
2060
|
-
signal: controller.signal
|
|
2061
|
-
});
|
|
2062
|
-
} catch (error) {
|
|
2063
|
-
clearTimeout(timeoutId);
|
|
2064
|
-
if (error instanceof DOMException && error.name === "AbortError") {
|
|
2065
|
-
if (params.signal?.aborted) {
|
|
2066
|
-
throw error;
|
|
2067
|
-
}
|
|
2068
|
-
throw new TimeoutError({
|
|
2069
|
-
code: ErrorCodes.TIMEOUT_LLM_RESPONSE,
|
|
2070
|
-
message: `OpenAI request timed out after ${timeoutMs}ms`,
|
|
2071
|
-
provider: "openai",
|
|
2072
|
-
recoverable: true,
|
|
2073
|
-
suggestion: "Try again or increase the timeout.",
|
|
2074
|
-
operationName: "openai.chatCompletions",
|
|
2075
|
-
timeoutMs
|
|
2076
|
-
});
|
|
2077
|
-
}
|
|
2078
|
-
throw new NetworkError({
|
|
2079
|
-
code: ErrorCodes.NETWORK_CONNECTION_LOST,
|
|
2080
|
-
message: `Failed to connect to OpenAI API: ${error.message}`,
|
|
2081
|
-
provider: "openai",
|
|
2082
|
-
suggestion: "Check your network connection and try again.",
|
|
2083
|
-
cause: error instanceof Error ? error : void 0
|
|
2084
|
-
});
|
|
2085
|
-
}
|
|
2086
|
-
clearTimeout(timeoutId);
|
|
2087
|
-
if (!response.ok) {
|
|
2088
|
-
await this.handleHttpError(response);
|
|
2089
|
-
}
|
|
2090
|
-
if (!response.body) {
|
|
2091
|
-
throw new NetworkError({
|
|
2092
|
-
code: ErrorCodes.NETWORK_CONNECTION_LOST,
|
|
2093
|
-
message: "OpenAI response body is null -- streaming unavailable.",
|
|
2094
|
-
provider: "openai",
|
|
2095
|
-
suggestion: "Retry the request."
|
|
2096
|
-
});
|
|
2097
|
-
}
|
|
2098
|
-
return { stream: response.body, response };
|
|
2099
|
-
}
|
|
2100
|
-
// -----------------------------------------------------------------------
|
|
2101
|
-
// Internal helpers
|
|
2102
|
-
// -----------------------------------------------------------------------
|
|
2103
|
-
/**
|
|
2104
|
-
* Extract `TextChunk` and accumulate `ToolCall` data from a single parsed
|
|
2105
|
-
* OpenAI SSE JSON object.
|
|
2106
|
-
*
|
|
2107
|
-
* OpenAI tool calls arrive incrementally: the first chunk for a tool call
|
|
2108
|
-
* carries the `id` and `function.name`, while subsequent chunks append to
|
|
2109
|
-
* `function.arguments`. We accumulate these in `pendingToolCalls` and only
|
|
2110
|
-
* yield complete `ToolCall` objects when the finish_reason is 'tool_calls'
|
|
2111
|
-
* or when flushed.
|
|
2112
|
-
*/
|
|
2113
|
-
*extractChunks(parsed, pendingToolCalls, doneEmitted) {
|
|
2114
|
-
this.lastExtractEmittedDone = false;
|
|
2115
|
-
const choices = parsed.choices;
|
|
2116
|
-
if (!choices || choices.length === 0) return;
|
|
2117
|
-
for (const choice of choices) {
|
|
2118
|
-
const delta = choice.delta;
|
|
2119
|
-
const finishReason = choice.finish_reason;
|
|
2120
|
-
if (delta) {
|
|
2121
|
-
if (typeof delta.content === "string" && delta.content !== "") {
|
|
2122
|
-
yield {
|
|
2123
|
-
text: delta.content,
|
|
2124
|
-
done: false
|
|
2125
|
-
};
|
|
2126
|
-
}
|
|
2127
|
-
const toolCallDeltas = delta.tool_calls;
|
|
2128
|
-
if (toolCallDeltas) {
|
|
2129
|
-
for (const tc of toolCallDeltas) {
|
|
2130
|
-
const existing = pendingToolCalls.get(tc.index);
|
|
2131
|
-
if (existing) {
|
|
2132
|
-
if (tc.function?.arguments) {
|
|
2133
|
-
existing.argumentsJson += tc.function.arguments;
|
|
2134
|
-
}
|
|
2135
|
-
} else {
|
|
2136
|
-
pendingToolCalls.set(tc.index, {
|
|
2137
|
-
id: tc.id ?? "",
|
|
2138
|
-
name: tc.function?.name ?? "",
|
|
2139
|
-
argumentsJson: tc.function?.arguments ?? ""
|
|
2140
|
-
});
|
|
2141
|
-
}
|
|
2142
|
-
}
|
|
2143
|
-
}
|
|
2144
|
-
}
|
|
2145
|
-
if (finishReason === "tool_calls") {
|
|
2146
|
-
yield* this.flushPendingToolCalls(pendingToolCalls);
|
|
2147
|
-
}
|
|
2148
|
-
if (finishReason === "stop" && !doneEmitted && !this.lastExtractEmittedDone) {
|
|
2149
|
-
this.lastExtractEmittedDone = true;
|
|
2150
|
-
yield { text: "", done: true };
|
|
2151
|
-
}
|
|
2152
|
-
}
|
|
2153
|
-
}
|
|
2154
|
-
/**
|
|
2155
|
-
* Flush all accumulated pending tool calls as complete `ToolCall` objects.
|
|
2156
|
-
*/
|
|
2157
|
-
*flushPendingToolCalls(pendingToolCalls) {
|
|
2158
|
-
const sorted = [...pendingToolCalls.entries()].sort(
|
|
2159
|
-
([a], [b]) => a - b
|
|
2160
|
-
);
|
|
2161
|
-
for (const [, tc] of sorted) {
|
|
2162
|
-
let args = {};
|
|
2163
|
-
try {
|
|
2164
|
-
args = JSON.parse(tc.argumentsJson);
|
|
2165
|
-
} catch (_e) {
|
|
2166
|
-
console.warn("[GuideKit:LLM] Failed to parse tool call arguments:", tc.argumentsJson);
|
|
2167
|
-
}
|
|
2168
|
-
yield {
|
|
2169
|
-
id: tc.id,
|
|
2170
|
-
name: tc.name,
|
|
2171
|
-
arguments: args
|
|
2172
|
-
};
|
|
2173
|
-
}
|
|
2174
|
-
pendingToolCalls.clear();
|
|
2175
|
-
}
|
|
2176
|
-
/**
|
|
2177
|
-
* Extract token usage from a parsed OpenAI response chunk.
|
|
2178
|
-
* Usage data typically appears in the final chunk when `stream_options`
|
|
2179
|
-
* includes `include_usage`, or in the non-streaming response.
|
|
2180
|
-
* Returns `null` if no usage data is present.
|
|
2181
|
-
*/
|
|
2182
|
-
extractUsage(parsed) {
|
|
2183
|
-
const usage = parsed.usage;
|
|
2184
|
-
if (!usage) return null;
|
|
2185
|
-
return {
|
|
2186
|
-
prompt: usage.prompt_tokens ?? 0,
|
|
2187
|
-
completion: usage.completion_tokens ?? 0,
|
|
2188
|
-
total: usage.total_tokens ?? 0
|
|
2189
|
-
};
|
|
2190
|
-
}
|
|
2191
|
-
/**
|
|
2192
|
-
* Check whether a parsed OpenAI chunk indicates the response was
|
|
2193
|
-
* blocked by a content filter.
|
|
2194
|
-
*
|
|
2195
|
-
* OpenAI signals content filtering through:
|
|
2196
|
-
* - `choices[].finish_reason === 'content_filter'`
|
|
2197
|
-
* - `choices[].content_filter_results` with `filtered: true`
|
|
2198
|
-
*/
|
|
2199
|
-
isContentFiltered(parsed) {
|
|
2200
|
-
const choices = parsed.choices;
|
|
2201
|
-
if (!choices || choices.length === 0) return false;
|
|
2202
|
-
return choices.some((choice) => {
|
|
2203
|
-
if (choice.finish_reason === "content_filter") return true;
|
|
2204
|
-
const filterResults = choice.content_filter_results;
|
|
2205
|
-
if (filterResults) {
|
|
2206
|
-
return Object.values(filterResults).some((r) => r.filtered === true);
|
|
2207
|
-
}
|
|
2208
|
-
return false;
|
|
2209
|
-
});
|
|
2210
|
-
}
|
|
2211
|
-
/**
|
|
2212
|
-
* Translate an HTTP error response from OpenAI into the appropriate
|
|
2213
|
-
* GuideKit error class.
|
|
2214
|
-
*/
|
|
2215
|
-
async handleHttpError(response) {
|
|
2216
|
-
let errorBody = "";
|
|
2217
|
-
try {
|
|
2218
|
-
errorBody = await response.text();
|
|
2219
|
-
} catch {
|
|
2220
|
-
}
|
|
2221
|
-
const status = response.status;
|
|
2222
|
-
if (status === 401 || status === 403) {
|
|
2223
|
-
throw new AuthenticationError({
|
|
2224
|
-
code: ErrorCodes.AUTH_INVALID_KEY,
|
|
2225
|
-
message: `OpenAI API authentication failed (${status}): ${errorBody}`,
|
|
2226
|
-
provider: "openai",
|
|
2227
|
-
suggestion: "Verify your OpenAI API key is correct and has not expired."
|
|
2228
|
-
});
|
|
2229
|
-
}
|
|
2230
|
-
if (status === 429) {
|
|
2231
|
-
const retryAfterHeader = response.headers.get("retry-after");
|
|
2232
|
-
const retryAfterMs = retryAfterHeader ? parseInt(retryAfterHeader, 10) * 1e3 : 6e4;
|
|
2233
|
-
throw new RateLimitError({
|
|
2234
|
-
code: ErrorCodes.RATE_LIMIT_PROVIDER,
|
|
2235
|
-
message: `OpenAI API rate limit exceeded (429): ${errorBody}`,
|
|
2236
|
-
provider: "openai",
|
|
2237
|
-
recoverable: true,
|
|
2238
|
-
suggestion: `Rate limited by OpenAI. Retry after ${Math.ceil(retryAfterMs / 1e3)}s.`,
|
|
2239
|
-
retryAfterMs
|
|
2240
|
-
});
|
|
2241
|
-
}
|
|
2242
|
-
if (status >= 500) {
|
|
2243
|
-
throw new NetworkError({
|
|
2244
|
-
code: ErrorCodes.NETWORK_CONNECTION_LOST,
|
|
2245
|
-
message: `OpenAI API server error (${status}): ${errorBody}`,
|
|
2246
|
-
provider: "openai",
|
|
2247
|
-
suggestion: "The OpenAI API is experiencing issues. Please try again later."
|
|
2248
|
-
});
|
|
2249
|
-
}
|
|
2250
|
-
throw new NetworkError({
|
|
2251
|
-
code: ErrorCodes.NETWORK_CONNECTION_LOST,
|
|
2252
|
-
message: `OpenAI API request failed (${status}): ${errorBody}`,
|
|
2253
|
-
provider: "openai",
|
|
2254
|
-
suggestion: "Check the request parameters and try again."
|
|
2255
|
-
});
|
|
2256
|
-
}
|
|
2257
|
-
};
|
|
2258
|
-
|
|
2259
1844
|
// src/llm/index.ts
|
|
2260
1845
|
var DEFAULT_GEMINI_MODEL = "gemini-2.5-flash";
|
|
2261
|
-
var
|
|
1846
|
+
var DEFAULT_TIMEOUT_MS = 15e3;
|
|
2262
1847
|
var GEMINI_BASE_URL = "https://generativelanguage.googleapis.com/v1beta/models";
|
|
2263
1848
|
var DEFAULT_SAFETY_SETTINGS = [
|
|
2264
1849
|
{ category: "HARM_CATEGORY_HARASSMENT", threshold: "BLOCK_ONLY_HIGH" },
|
|
@@ -2266,7 +1851,7 @@ var DEFAULT_SAFETY_SETTINGS = [
|
|
|
2266
1851
|
{ category: "HARM_CATEGORY_SEXUALLY_EXPLICIT", threshold: "BLOCK_ONLY_HIGH" },
|
|
2267
1852
|
{ category: "HARM_CATEGORY_DANGEROUS_CONTENT", threshold: "BLOCK_ONLY_HIGH" }
|
|
2268
1853
|
];
|
|
2269
|
-
function
|
|
1854
|
+
function emptyUsage() {
|
|
2270
1855
|
return { prompt: 0, completion: 0, total: 0 };
|
|
2271
1856
|
}
|
|
2272
1857
|
var GeminiAdapter = class {
|
|
@@ -2277,7 +1862,7 @@ var GeminiAdapter = class {
|
|
|
2277
1862
|
* Updated as each SSE chunk is parsed; the final value reflects the
|
|
2278
1863
|
* cumulative usage metadata sent by Gemini (typically in the last chunk).
|
|
2279
1864
|
*/
|
|
2280
|
-
_lastUsage =
|
|
1865
|
+
_lastUsage = emptyUsage();
|
|
2281
1866
|
constructor(config) {
|
|
2282
1867
|
this.apiKey = config.apiKey;
|
|
2283
1868
|
this.model = config.model ?? DEFAULT_GEMINI_MODEL;
|
|
@@ -2335,7 +1920,7 @@ var GeminiAdapter = class {
|
|
|
2335
1920
|
const reader = stream.getReader();
|
|
2336
1921
|
const decoder = new TextDecoder();
|
|
2337
1922
|
let buffer = "";
|
|
2338
|
-
this._lastUsage =
|
|
1923
|
+
this._lastUsage = emptyUsage();
|
|
2339
1924
|
try {
|
|
2340
1925
|
while (true) {
|
|
2341
1926
|
const { done, value } = await reader.read();
|
|
@@ -2444,7 +2029,7 @@ var GeminiAdapter = class {
|
|
|
2444
2029
|
if (params.tools) {
|
|
2445
2030
|
body.tools = params.tools;
|
|
2446
2031
|
}
|
|
2447
|
-
const timeoutMs = params.timeoutMs ??
|
|
2032
|
+
const timeoutMs = params.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
2448
2033
|
const controller = new AbortController();
|
|
2449
2034
|
const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
|
|
2450
2035
|
if (params.signal) {
|
|
@@ -2719,7 +2304,7 @@ var LLMOrchestrator = class {
|
|
|
2719
2304
|
}
|
|
2720
2305
|
}
|
|
2721
2306
|
this.callbacks.onChunk?.({ text: "", done: true });
|
|
2722
|
-
let usage =
|
|
2307
|
+
let usage = emptyUsage();
|
|
2723
2308
|
if ("lastUsage" in adapter) {
|
|
2724
2309
|
usage = adapter.lastUsage;
|
|
2725
2310
|
}
|
|
@@ -2739,7 +2324,7 @@ var LLMOrchestrator = class {
|
|
|
2739
2324
|
*
|
|
2740
2325
|
* Custom adapters:
|
|
2741
2326
|
* - Pass `{ adapter: myAdapter }` to use any `LLMProviderAdapter`.
|
|
2742
|
-
* Example: `llm: { adapter:
|
|
2327
|
+
* Example: `llm: { adapter: myCustomAdapter }`
|
|
2743
2328
|
*/
|
|
2744
2329
|
createAdapter(config) {
|
|
2745
2330
|
if ("adapter" in config) {
|
|
@@ -9637,7 +9222,6 @@ exports.InitializationError = InitializationError;
|
|
|
9637
9222
|
exports.LLMOrchestrator = LLMOrchestrator;
|
|
9638
9223
|
exports.NavigationController = NavigationController;
|
|
9639
9224
|
exports.NetworkError = NetworkError;
|
|
9640
|
-
exports.OpenAIAdapter = OpenAIAdapter;
|
|
9641
9225
|
exports.PermissionError = PermissionError;
|
|
9642
9226
|
exports.ProactiveTriggerEngine = ProactiveTriggerEngine;
|
|
9643
9227
|
exports.RateLimitError = RateLimitError;
|