@sayknow-cli/ai 0.5.2 → 0.5.8

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.
@@ -0,0 +1,39 @@
1
+ /**
2
+ * SGLang login flow.
3
+ *
4
+ * SGLang commonly exposes an OpenAI-compatible API on a local server. It may
5
+ * require a bearer token, but local servers commonly allow unauthenticated
6
+ * access. This flow stores an API-key-style credential for auth storage.
7
+ */
8
+
9
+ import type { OAuthController, OAuthProvider } from "./types";
10
+
11
+ const PROVIDER_ID: OAuthProvider = "sglang";
12
+ const AUTH_URL = "https://docs.sglang.io/docs/advanced_features/server_arguments.html";
13
+ const DEFAULT_LOCAL_BASE_URL = "http://127.0.0.1:30000/v1";
14
+
15
+ /**
16
+ * Login to SGLang with an explicit bearer token.
17
+ */
18
+ export async function loginSglang(options: OAuthController): Promise<string> {
19
+ if (!options.onPrompt) {
20
+ throw new Error(`${PROVIDER_ID} login requires onPrompt callback`);
21
+ }
22
+ options.onAuth?.({
23
+ url: AUTH_URL,
24
+ instructions: `Paste the API key configured with SGLang's --api-key option. Local no-auth servers at ${DEFAULT_LOCAL_BASE_URL} are discovered automatically and do not need /login.`,
25
+ });
26
+ const apiKey = await options.onPrompt({
27
+ message: "Paste your SGLang API key",
28
+ placeholder: "SGLang API key",
29
+ allowEmpty: false,
30
+ });
31
+ if (options.signal?.aborted) {
32
+ throw new Error("Login cancelled");
33
+ }
34
+ const trimmed = apiKey.trim();
35
+ if (!trimmed) {
36
+ throw new Error("SGLang API key is required; local no-auth servers are discovered automatically");
37
+ }
38
+ return trimmed;
39
+ }
@@ -52,6 +52,7 @@ export type OAuthProvider =
52
52
  | "venice"
53
53
  | "vercel-ai-gateway"
54
54
  | "vllm"
55
+ | "sglang"
55
56
  | "xai"
56
57
  | "glm-zcode"
57
58
  | "xiaomi"
@@ -127,8 +127,12 @@ const EMPTY_RESPONSE_USAGE_THRESHOLD = 5;
127
127
  * misleading error text.
128
128
  */
129
129
  const OVERFLOW_PROVIDER_CODES = new Set(["context_length_exceeded", "request_too_large"]);
130
+ /**
131
+ * Codes that name a specific non-overflow *cause*. These are authoritative and
132
+ * can never be upgraded by error prose. Generic HTTP envelope types belong in
133
+ * {@link GENERIC_ENVELOPE_PROVIDER_CODES} instead.
134
+ */
130
135
  const NON_OVERFLOW_PROVIDER_CODES = new Set([
131
- "invalid_request_error",
132
136
  "authentication_error",
133
137
  "invalid_api_key",
134
138
  "invalid_token",
@@ -145,6 +149,7 @@ const NON_OVERFLOW_PROVIDER_CODES = new Set([
145
149
  "rate_limit_error",
146
150
  "rate_limit_exceeded",
147
151
  "too_many_requests",
152
+ "empty_response",
148
153
  ]);
149
154
 
150
155
  function transportCodes(transportFailure: TransportFailureFacts | undefined): string[] {
@@ -157,6 +162,53 @@ function hasTypedNonOverflowCode(transportFailure: TransportFailureFacts | undef
157
162
  return transportCodes(transportFailure).some(code => NON_OVERFLOW_PROVIDER_CODES.has(code));
158
163
  }
159
164
 
165
+ /**
166
+ * Generic envelope codes that name the HTTP error *category*, not its cause.
167
+ *
168
+ * Anthropic reports context overflow through this envelope:
169
+ *
170
+ * {"type":"error","error":{"type":"invalid_request_error",
171
+ * "message":"prompt is too long: 1158066 tokens > 1000000 maximum"}}
172
+ *
173
+ * Treating the envelope as an authoritative non-overflow cause vetoed the
174
+ * overflow classification, so auto-compaction never ran and the session died on
175
+ * the very overflow it was supposed to absorb.
176
+ *
177
+ * Unlike {@link NON_OVERFLOW_PROVIDER_CODES} (auth, quota, rate limit), this
178
+ * envelope names no cause, so it must not veto an overflow the provider stated
179
+ * quantitatively. It still vetoes free-form prose: only the self-verifying
180
+ * measured form below can override it.
181
+ */
182
+ const GENERIC_ENVELOPE_PROVIDER_CODES = new Set(["invalid_request_error"]);
183
+
184
+ /**
185
+ * Anthropic's measured overflow report: `<used> tokens > <limit> maximum`.
186
+ *
187
+ * Deliberately far narrower than {@link OVERFLOW_PATTERNS}. Those patterns
188
+ * include loose prose (`too many tokens`, `token limit exceeded`) that a tool
189
+ * result or a model-authored string can trivially contain, so they must never
190
+ * be able to flip a typed transport classification. This form carries its own
191
+ * arithmetic proof and is verified below, so injected text cannot satisfy it
192
+ * without also asserting a real overage.
193
+ */
194
+ const ANTHROPIC_MEASURED_OVERFLOW_PATTERN = /prompt is too long:\s*(\d+)\s*tokens?\s*>\s*(\d+)\s*maximum/i;
195
+
196
+ /**
197
+ * True only for a provider-measured overflow that verifies against itself:
198
+ * the reported usage must actually exceed the reported maximum.
199
+ */
200
+ function hasSelfVerifyingOverflowMeasurement(message: AssistantMessage): boolean {
201
+ if (message.stopReason !== "error") return false;
202
+ const errorMessage = message.errorMessage;
203
+ if (!errorMessage) return false;
204
+ const match = ANTHROPIC_MEASURED_OVERFLOW_PATTERN.exec(errorMessage);
205
+ if (!match) return false;
206
+ const used = Number(match[1]);
207
+ const maximum = Number(match[2]);
208
+ if (!Number.isFinite(used) || !Number.isFinite(maximum) || maximum <= 0) return false;
209
+ return used > maximum;
210
+ }
211
+
160
212
  function isTypedNoBodyOverflow(
161
213
  message: AssistantMessage,
162
214
  transportFailure: TransportFailureFacts | undefined,
@@ -173,7 +225,18 @@ export function classifyContextOverflow(
173
225
  if (transportFailure?.status === 429) return false;
174
226
  const typedCodes = transportCodes(transportFailure);
175
227
  if (typedCodes.some(code => OVERFLOW_PROVIDER_CODES.has(code))) return true;
228
+ // A specific non-overflow cause (auth, quota, rate limit) is authoritative
229
+ // and can never be upgraded by error prose.
176
230
  if (hasTypedNonOverflowCode(transportFailure)) return false;
231
+ // A generic envelope (`invalid_request_error`) names no cause. It still
232
+ // vetoes free-form overflow prose, but must not veto a provider-measured,
233
+ // self-verifying overflow report — that is how Anthropic reports overflow.
234
+ if (
235
+ typedCodes.some(code => GENERIC_ENVELOPE_PROVIDER_CODES.has(code)) &&
236
+ !hasSelfVerifyingOverflowMeasurement(message)
237
+ ) {
238
+ return false;
239
+ }
177
240
  if (isTypedNoBodyOverflow(message, transportFailure)) return true;
178
241
 
179
242
  const errorMessage = message.errorMessage;