@cryer/star-cli 0.3.5 → 0.3.7

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cryer/star-cli",
3
- "version": "0.3.5",
3
+ "version": "0.3.7",
4
4
  "description": "An AI agent command-line interface",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -1,122 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- // src/core/http-error.ts
4
- function responseBodyOf(error) {
5
- const body = error.responseBody;
6
- return typeof body === "string" && body.length > 0 ? body : void 0;
7
- }
8
-
9
- // src/llm/retry.ts
10
- var NON_RETRYABLE_ERROR_NAMES = /* @__PURE__ */ new Set([
11
- "AI_NoSuchToolError",
12
- "AI_InvalidToolArgumentsError",
13
- "AI_InvalidPromptError",
14
- "AI_NoSuchModelError",
15
- "AI_LoadAPIKeyError",
16
- "AI_TypeValidationError",
17
- "AI_JSONParseError"
18
- ]);
19
- var RETRYABLE_MESSAGE_PATTERNS = [
20
- /\b(408|425|429|500|502|503|504|520|521|522|523|524|529)\b/,
21
- /rate.?limit|too many requests|rate increased/i,
22
- /overloaded|service.?unavailable|internal (server )?error|server error|provider returned error|bad gateway|gateway timeout/i,
23
- /terminated|fetch failed|failed to fetch|network.?error|upstream connect|connection (?:error|refused|lost|reset)|socket (?:hang up|connection)|reset before headers|getaddrinfo|enotfound|eai_again|econnrefused|econnreset|etimedout|epipe|ehostunreach|enetunreach/i,
24
- /\b(?:request|response|connection|network|stream|read|headers?|body) (?:timeout|timed out|time out)\b/i,
25
- /resource.?exhausted|try (?:your request )?again|at capacity/i
26
- ];
27
- function statusCodeOf(error) {
28
- const status = error.statusCode;
29
- return typeof status === "number" ? status : void 0;
30
- }
31
- function matchesRetryablePattern(text) {
32
- return typeof text === "string" && RETRYABLE_MESSAGE_PATTERNS.some((p) => p.test(text));
33
- }
34
- function isRetryableStreamError(error) {
35
- if (NON_RETRYABLE_ERROR_NAMES.has(error.name)) return false;
36
- if (error.isRetryable === true) return true;
37
- const status = statusCodeOf(error);
38
- if (status !== void 0) {
39
- if (status === 408 || status === 429 || status >= 500 && status !== 501) return true;
40
- return matchesRetryablePattern(error.message) || matchesRetryablePattern(responseBodyOf(error));
41
- }
42
- return true;
43
- }
44
- var RETRY_JITTER_FACTOR = 0.25;
45
- var MAX_RETRY_DELAY_MS = 6e4;
46
- function retryAfterDelayMs(error) {
47
- const headers = error.responseHeaders;
48
- if (!headers || typeof headers !== "object") return void 0;
49
- const normalized = {};
50
- for (const [key, value] of Object.entries(headers)) {
51
- if (typeof value === "string") normalized[key.toLowerCase()] = value;
52
- }
53
- const afterMs = Number.parseFloat(normalized["retry-after-ms"] ?? "");
54
- if (!Number.isNaN(afterMs)) return Math.max(0, afterMs);
55
- const after = normalized["retry-after"];
56
- if (after) {
57
- const seconds = Number.parseFloat(after);
58
- if (!Number.isNaN(seconds)) return Math.max(0, Math.ceil(seconds * 1e3));
59
- const dateMs = Date.parse(after) - Date.now();
60
- if (!Number.isNaN(dateMs) && dateMs > 0) return Math.ceil(dateMs);
61
- }
62
- return void 0;
63
- }
64
- function computeRetryDelayMs(attempt, baseMs, error, random = Math.random()) {
65
- const hinted = error ? retryAfterDelayMs(error) : void 0;
66
- if (hinted !== void 0) return Math.min(hinted, MAX_RETRY_DELAY_MS);
67
- const base = baseMs * 2 ** attempt;
68
- return Math.min(Math.ceil(base + base * RETRY_JITTER_FACTOR * random), MAX_RETRY_DELAY_MS);
69
- }
70
- function summarizeStreamError(error) {
71
- const parts = [error.message || error.name];
72
- const status = statusCodeOf(error);
73
- if (status !== void 0 && !error.message.includes(String(status))) {
74
- parts.push(`(HTTP ${status})`);
75
- }
76
- const body = responseBodyOf(error);
77
- if (body && !error.message.includes(body.slice(0, 40))) {
78
- const excerpt = body.replace(/\s+/g, " ").trim().slice(0, 200);
79
- if (excerpt) parts.push(`\u2014 ${excerpt}`);
80
- }
81
- return parts.join(" ");
82
- }
83
-
84
- // src/config/paths.ts
85
- import os from "os";
86
- import path from "path";
87
- function starHome() {
88
- return process.env.STAR_HOME ?? path.join(os.homedir(), ".star-cli");
89
- }
90
- function globalConfigPath() {
91
- return path.join(starHome(), "config.toml");
92
- }
93
- function sessionsDir() {
94
- return path.join(starHome(), "sessions");
95
- }
96
- function userMemoryPath() {
97
- return path.join(starHome(), "MEMORY.md");
98
- }
99
- function gitTreesDir() {
100
- return path.join(starHome(), "git-trees");
101
- }
102
- function envFilePath() {
103
- return path.join(starHome(), ".env");
104
- }
105
- function projectConfigPath(cwd) {
106
- return path.join(cwd, ".star", "config.toml");
107
- }
108
-
109
- export {
110
- responseBodyOf,
111
- isRetryableStreamError,
112
- computeRetryDelayMs,
113
- summarizeStreamError,
114
- starHome,
115
- globalConfigPath,
116
- sessionsDir,
117
- userMemoryPath,
118
- gitTreesDir,
119
- envFilePath,
120
- projectConfigPath
121
- };
122
- //# sourceMappingURL=chunk-B6P4VUBE.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/core/http-error.ts","../src/llm/retry.ts","../src/config/paths.ts"],"sourcesContent":["// The AI SDK's APICallError carries the raw response body; relays often put\n// the real failure reason there while the message stays generic. Shared by\n// the retry policy (llm/retry.ts) and the oversized-image detector\n// (core/image.ts) — kept in core so neither has to import across layers.\nexport function responseBodyOf(error: Error): string | undefined {\n const body = (error as { responseBody?: unknown }).responseBody;\n return typeof body === \"string\" && body.length > 0 ? body : undefined;\n}\n","// Retry classification and backoff for model requests, modeled on opencode's\n// SessionRetry (sst/opencode, packages/opencode/src/session/retry.ts). Relays\n// and gateways report transient failures inconsistently — a Cloudflare HTML\n// 403, a 400 whose body says \"provider returned error\", a 200 stream that\n// dies mid-way — so classification looks past the status code at the error\n// message and response body, and the wait between attempts honors Retry-After\n// headers with jittered exponential backoff as the fallback.\nimport { responseBodyOf } from \"../core/http-error\";\n\n// Client/validation failures will fail again identically, so only transient\n// conditions merit another attempt. SDK validation errors never retry —\n// TypeValidation/JSONParse mean the relay answered with a malformed payload,\n// which a resend would burn the whole retry budget on.\nconst NON_RETRYABLE_ERROR_NAMES = new Set([\n \"AI_NoSuchToolError\",\n \"AI_InvalidToolArgumentsError\",\n \"AI_InvalidPromptError\",\n \"AI_NoSuchModelError\",\n \"AI_LoadAPIKeyError\",\n \"AI_TypeValidationError\",\n \"AI_JSONParseError\",\n]);\n\nconst RETRYABLE_MESSAGE_PATTERNS = [\n /\\b(408|425|429|500|502|503|504|520|521|522|523|524|529)\\b/,\n /rate.?limit|too many requests|rate increased/i,\n /overloaded|service.?unavailable|internal (server )?error|server error|provider returned error|bad gateway|gateway timeout/i,\n /terminated|fetch failed|failed to fetch|network.?error|upstream connect|connection (?:error|refused|lost|reset)|socket (?:hang up|connection)|reset before headers|getaddrinfo|enotfound|eai_again|econnrefused|econnreset|etimedout|epipe|ehostunreach|enetunreach/i,\n /\\b(?:request|response|connection|network|stream|read|headers?|body) (?:timeout|timed out|time out)\\b/i,\n /resource.?exhausted|try (?:your request )?again|at capacity/i,\n];\n\nfunction statusCodeOf(error: Error): number | undefined {\n const status = (error as { statusCode?: unknown }).statusCode;\n return typeof status === \"number\" ? status : undefined;\n}\n\nfunction matchesRetryablePattern(text: string | undefined): boolean {\n return typeof text === \"string\" && RETRYABLE_MESSAGE_PATTERNS.some((p) => p.test(text));\n}\n\nexport function isRetryableStreamError(error: Error): boolean {\n if (NON_RETRYABLE_ERROR_NAMES.has(error.name)) return false;\n // The SDK's own classification (it marks 429/5xx API call errors retryable).\n if ((error as { isRetryable?: unknown }).isRetryable === true) return true;\n const status = statusCodeOf(error);\n if (status !== undefined) {\n // 501 Not Implemented is excluded from the 5xx blanket: the endpoint\n // cannot serve this request shape at all, so it falls through to the\n // message/body check like any other deterministic status.\n if (status === 408 || status === 429 || (status >= 500 && status !== 501)) return true;\n // A 4xx is normally deterministic, but gateways and relays surface\n // transient failures under 4xx codes (Cloudflare rate-limit 403s, a 400\n // wrapping an upstream failure), recognizable only by what the error says.\n return matchesRetryablePattern(error.message) || matchesRetryablePattern(responseBodyOf(error));\n }\n // No status at all: network/parse failure before or during the response.\n return true;\n}\n\nexport const RETRY_JITTER_FACTOR = 0.25;\n// The wait between attempts never exceeds this, even when a Retry-After\n// header asks for more: an interactive CLI turn cannot park for minutes.\nexport const MAX_RETRY_DELAY_MS = 60_000;\n\n// Parses the Retry-After hints an APICallError can carry: retry-after-ms,\n// then retry-after in seconds or as an HTTP date. Header names from the AI\n// SDK are already lowercase, but normalize defensively.\nexport function retryAfterDelayMs(error: Error): number | undefined {\n const headers = (error as { responseHeaders?: unknown }).responseHeaders;\n if (!headers || typeof headers !== \"object\") return undefined;\n const normalized: Record<string, string> = {};\n for (const [key, value] of Object.entries(headers)) {\n if (typeof value === \"string\") normalized[key.toLowerCase()] = value;\n }\n const afterMs = Number.parseFloat(normalized[\"retry-after-ms\"] ?? \"\");\n if (!Number.isNaN(afterMs)) return Math.max(0, afterMs);\n const after = normalized[\"retry-after\"];\n if (after) {\n const seconds = Number.parseFloat(after);\n if (!Number.isNaN(seconds)) return Math.max(0, Math.ceil(seconds * 1000));\n const dateMs = Date.parse(after) - Date.now();\n if (!Number.isNaN(dateMs) && dateMs > 0) return Math.ceil(dateMs);\n }\n return undefined;\n}\n\n// Milliseconds to wait before the next attempt: the server's Retry-After hint\n// when present (capped), otherwise exponential backoff (base × 2^attempt)\n// with +0–25% jitter so concurrent clients do not retry in lockstep.\nexport function computeRetryDelayMs(\n attempt: number,\n baseMs: number,\n error?: Error | null,\n random: number = Math.random(),\n): number {\n const hinted = error ? retryAfterDelayMs(error) : undefined;\n if (hinted !== undefined) return Math.min(hinted, MAX_RETRY_DELAY_MS);\n const base = baseMs * 2 ** attempt;\n return Math.min(Math.ceil(base + base * RETRY_JITTER_FACTOR * random), MAX_RETRY_DELAY_MS);\n}\n\n// One-line description of a stream failure for retry notices and logs: the\n// message, the HTTP status when the message omits it, and an excerpt of the\n// response body when it adds information the message does not have.\nexport function summarizeStreamError(error: Error): string {\n const parts = [error.message || error.name];\n const status = statusCodeOf(error);\n if (status !== undefined && !error.message.includes(String(status))) {\n parts.push(`(HTTP ${status})`);\n }\n const body = responseBodyOf(error);\n if (body && !error.message.includes(body.slice(0, 40))) {\n const excerpt = body.replace(/\\s+/g, \" \").trim().slice(0, 200);\n if (excerpt) parts.push(`— ${excerpt}`);\n }\n return parts.join(\" \");\n}\n","import os from \"node:os\";\nimport path from \"node:path\";\n\nexport function starHome(): string {\n return process.env.STAR_HOME ?? path.join(os.homedir(), \".star-cli\");\n}\n\nexport function globalConfigPath(): string {\n return path.join(starHome(), \"config.toml\");\n}\n\nexport function sessionsDir(): string {\n return path.join(starHome(), \"sessions\");\n}\n\nexport function userMemoryPath(): string {\n return path.join(starHome(), \"MEMORY.md\");\n}\n\n// Internal bare git repositories tracking the whole working tree per project\n// directory (one per cwd, keyed by base64url of the resolved path) for the\n// git-snapshot /undo//redo path. Kept outside the user's own .git.\nexport function gitTreesDir(): string {\n return path.join(starHome(), \"git-trees\");\n}\n\n// dotenv-style API key store written by /connect; config.toml only references\n// the variable names (apiKeyEnv), never the keys themselves.\nexport function envFilePath(): string {\n return path.join(starHome(), \".env\");\n}\n\nexport function projectConfigPath(cwd: string): string {\n return path.join(cwd, \".star\", \"config.toml\");\n}\n"],"mappings":"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","names":[]}