@steipete/summarize 0.7.1 → 0.8.1
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/CHANGELOG.md +46 -0
- package/README.md +53 -2
- package/dist/cli.js +3 -0
- package/dist/esm/cache.js +353 -0
- package/dist/esm/cache.js.map +1 -0
- package/dist/esm/config.js +78 -1
- package/dist/esm/config.js.map +1 -1
- package/dist/esm/content/asset.js +11 -17
- package/dist/esm/content/asset.js.map +1 -1
- package/dist/esm/daemon/auto-mode.js +8 -0
- package/dist/esm/daemon/auto-mode.js.map +1 -0
- package/dist/esm/daemon/cli.js +284 -0
- package/dist/esm/daemon/cli.js.map +1 -0
- package/dist/esm/daemon/config.js +82 -0
- package/dist/esm/daemon/config.js.map +1 -0
- package/dist/esm/daemon/constants.js +8 -0
- package/dist/esm/daemon/constants.js.map +1 -0
- package/dist/esm/daemon/env-merge.js +4 -0
- package/dist/esm/daemon/env-merge.js.map +1 -0
- package/dist/esm/daemon/env-snapshot.js +43 -0
- package/dist/esm/daemon/env-snapshot.js.map +1 -0
- package/dist/esm/daemon/flow-context.js +265 -0
- package/dist/esm/daemon/flow-context.js.map +1 -0
- package/dist/esm/daemon/launchd.js +149 -0
- package/dist/esm/daemon/launchd.js.map +1 -0
- package/dist/esm/daemon/meta.js +35 -0
- package/dist/esm/daemon/meta.js.map +1 -0
- package/dist/esm/daemon/models.js +175 -0
- package/dist/esm/daemon/models.js.map +1 -0
- package/dist/esm/daemon/request-settings.js +91 -0
- package/dist/esm/daemon/request-settings.js.map +1 -0
- package/dist/esm/daemon/schtasks.js +108 -0
- package/dist/esm/daemon/schtasks.js.map +1 -0
- package/dist/esm/daemon/server.js +399 -0
- package/dist/esm/daemon/server.js.map +1 -0
- package/dist/esm/daemon/summarize-progress.js +57 -0
- package/dist/esm/daemon/summarize-progress.js.map +1 -0
- package/dist/esm/daemon/summarize.js +263 -0
- package/dist/esm/daemon/summarize.js.map +1 -0
- package/dist/esm/daemon/systemd.js +117 -0
- package/dist/esm/daemon/systemd.js.map +1 -0
- package/dist/esm/flags.js +3 -1
- package/dist/esm/flags.js.map +1 -1
- package/dist/esm/llm/generate-text.js +445 -154
- package/dist/esm/llm/generate-text.js.map +1 -1
- package/dist/esm/llm/html-to-markdown.js +4 -1
- package/dist/esm/llm/html-to-markdown.js.map +1 -1
- package/dist/esm/llm/prompt.js +14 -0
- package/dist/esm/llm/prompt.js.map +1 -0
- package/dist/esm/llm/transcript-to-markdown.js +57 -0
- package/dist/esm/llm/transcript-to-markdown.js.map +1 -0
- package/dist/esm/model-spec.js +2 -2
- package/dist/esm/model-spec.js.map +1 -1
- package/dist/esm/run/attachments.js +10 -42
- package/dist/esm/run/attachments.js.map +1 -1
- package/dist/esm/run/cache-state.js +48 -0
- package/dist/esm/run/cache-state.js.map +1 -0
- package/dist/esm/run/cli-preflight.js +15 -1
- package/dist/esm/run/cli-preflight.js.map +1 -1
- package/dist/esm/run/cookies/twitter.js +224 -0
- package/dist/esm/run/cookies/twitter.js.map +1 -0
- package/dist/esm/run/fetch-with-timeout.js +1 -1
- package/dist/esm/run/fetch-with-timeout.js.map +1 -1
- package/dist/esm/run/finish-line.js +46 -17
- package/dist/esm/run/finish-line.js.map +1 -1
- package/dist/esm/run/flows/asset/input.js +2 -4
- package/dist/esm/run/flows/asset/input.js.map +1 -1
- package/dist/esm/run/flows/asset/preprocess.js +52 -72
- package/dist/esm/run/flows/asset/preprocess.js.map +1 -1
- package/dist/esm/run/flows/asset/summary.js +127 -47
- package/dist/esm/run/flows/asset/summary.js.map +1 -1
- package/dist/esm/run/flows/url/extract.js +6 -1
- package/dist/esm/run/flows/url/extract.js.map +1 -1
- package/dist/esm/run/flows/url/flow.js +166 -85
- package/dist/esm/run/flows/url/flow.js.map +1 -1
- package/dist/esm/run/flows/url/markdown.js +88 -46
- package/dist/esm/run/flows/url/markdown.js.map +1 -1
- package/dist/esm/run/flows/url/summary.js +263 -185
- package/dist/esm/run/flows/url/summary.js.map +1 -1
- package/dist/esm/run/help.js +33 -2
- package/dist/esm/run/help.js.map +1 -1
- package/dist/esm/run/run-env.js +36 -2
- package/dist/esm/run/run-env.js.map +1 -1
- package/dist/esm/run/runner.js +362 -227
- package/dist/esm/run/runner.js.map +1 -1
- package/dist/esm/run/summary-engine.js +21 -6
- package/dist/esm/run/summary-engine.js.map +1 -1
- package/dist/esm/run/summary-llm.js +4 -1
- package/dist/esm/run/summary-llm.js.map +1 -1
- package/dist/esm/tty/format.js +9 -0
- package/dist/esm/tty/format.js.map +1 -1
- package/dist/esm/version.js +1 -1
- package/dist/types/cache.d.ts +70 -0
- package/dist/types/config.d.ts +46 -0
- package/dist/types/content/asset.d.ts +4 -3
- package/dist/types/daemon/auto-mode.d.ts +8 -0
- package/dist/types/daemon/cli.d.ts +9 -0
- package/dist/types/daemon/config.d.ts +19 -0
- package/dist/types/daemon/constants.d.ts +7 -0
- package/dist/types/daemon/env-merge.d.ts +5 -0
- package/dist/types/daemon/env-snapshot.d.ts +4 -0
- package/dist/types/daemon/flow-context.d.ts +28 -0
- package/dist/types/daemon/launchd.d.ts +29 -0
- package/dist/types/daemon/meta.d.ts +12 -0
- package/dist/types/daemon/models.d.ts +27 -0
- package/dist/types/daemon/request-settings.d.ts +27 -0
- package/dist/types/daemon/schtasks.d.ts +16 -0
- package/dist/types/daemon/server.d.ts +12 -0
- package/dist/types/daemon/summarize-progress.d.ts +2 -0
- package/dist/types/daemon/summarize.d.ts +59 -0
- package/dist/types/daemon/systemd.d.ts +16 -0
- package/dist/types/flags.d.ts +1 -1
- package/dist/types/llm/generate-text.d.ts +11 -5
- package/dist/types/llm/html-to-markdown.d.ts +4 -1
- package/dist/types/llm/prompt.d.ts +9 -0
- package/dist/types/llm/transcript-to-markdown.d.ts +34 -0
- package/dist/types/run/attachments.d.ts +4 -10
- package/dist/types/run/cache-state.d.ts +12 -0
- package/dist/types/run/cli-preflight.d.ts +1 -0
- package/dist/types/run/cookies/twitter.d.ts +17 -0
- package/dist/types/run/finish-line.d.ts +31 -1
- package/dist/types/run/flows/asset/preprocess.d.ts +5 -2
- package/dist/types/run/flows/asset/summary.d.ts +11 -0
- package/dist/types/run/flows/url/markdown.d.ts +3 -0
- package/dist/types/run/flows/url/summary.d.ts +6 -3
- package/dist/types/run/flows/url/types.d.ts +52 -18
- package/dist/types/run/help.d.ts +1 -0
- package/dist/types/run/run-env.d.ts +6 -0
- package/dist/types/run/summary-engine.d.ts +8 -2
- package/dist/types/run/summary-llm.d.ts +6 -3
- package/dist/types/tty/format.d.ts +1 -0
- package/dist/types/version.d.ts +1 -1
- package/docs/README.md +5 -0
- package/docs/cache.md +72 -0
- package/docs/chrome-extension.md +180 -0
- package/docs/cli.md +6 -0
- package/docs/config.md +65 -1
- package/docs/extract-only.md +6 -0
- package/docs/firecrawl.md +6 -0
- package/docs/language.md +6 -0
- package/docs/llm.md +20 -0
- package/docs/manual-tests.md +6 -0
- package/docs/model-auto.md +6 -0
- package/docs/openai.md +6 -0
- package/docs/site/index.html +11 -1
- package/docs/smoketest.md +6 -0
- package/docs/website.md +6 -0
- package/docs/youtube.md +9 -2
- package/package.json +7 -10
- package/dist/cli.cjs +0 -80566
- package/dist/cli.cjs.map +0 -7
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { countTokens } from 'gpt-tokenizer';
|
|
2
2
|
import { render as renderMarkdownAnsi } from 'markdansi';
|
|
3
|
+
import { buildLanguageKey, buildLengthKey, buildPromptHash, buildSummaryCacheKey, hashString, normalizeContentForHash, } from '../../../cache.js';
|
|
3
4
|
import { formatOutputLanguageForJson } from '../../../language.js';
|
|
5
|
+
import { parseGatewayStyleModelId } from '../../../llm/model-id.js';
|
|
4
6
|
import { buildAutoModelAttempts } from '../../../model-auto.js';
|
|
5
7
|
import { buildLinkSummaryPrompt } from '../../../prompts/index.js';
|
|
6
8
|
import { parseCliUserModelId } from '../../env.js';
|
|
@@ -10,7 +12,7 @@ import { prepareMarkdownForTerminal } from '../../markdown.js';
|
|
|
10
12
|
import { runModelAttempts } from '../../model-attempts.js';
|
|
11
13
|
import { buildOpenRouterNoAllowedProvidersMessage } from '../../openrouter.js';
|
|
12
14
|
import { isRichTty, markdownRenderWidth, supportsColor } from '../../terminal.js';
|
|
13
|
-
export function buildUrlPrompt({ extracted, outputLanguage, lengthArg, }) {
|
|
15
|
+
export function buildUrlPrompt({ extracted, outputLanguage, lengthArg, promptOverride, lengthInstruction, languageInstruction, }) {
|
|
14
16
|
const isYouTube = extracted.siteName === 'YouTube';
|
|
15
17
|
return buildLinkSummaryPrompt({
|
|
16
18
|
url: extracted.url,
|
|
@@ -18,12 +20,15 @@ export function buildUrlPrompt({ extracted, outputLanguage, lengthArg, }) {
|
|
|
18
20
|
siteName: extracted.siteName,
|
|
19
21
|
description: extracted.description,
|
|
20
22
|
content: extracted.content,
|
|
21
|
-
truncated:
|
|
23
|
+
truncated: extracted.truncated,
|
|
22
24
|
hasTranscript: isYouTube ||
|
|
23
25
|
(extracted.transcriptSource !== null && extracted.transcriptSource !== 'unavailable'),
|
|
24
26
|
summaryLength: lengthArg.kind === 'preset' ? lengthArg.preset : { maxCharacters: lengthArg.maxCharacters },
|
|
25
27
|
outputLanguage,
|
|
26
28
|
shares: [],
|
|
29
|
+
promptOverride: promptOverride ?? null,
|
|
30
|
+
lengthInstruction: lengthInstruction ?? null,
|
|
31
|
+
languageInstruction: languageInstruction ?? null,
|
|
27
32
|
});
|
|
28
33
|
}
|
|
29
34
|
const buildFinishExtras = ({ extracted, metricsDetailed, transcriptionCostLabel, }) => {
|
|
@@ -47,315 +52,386 @@ const pickModelForFinishLine = (llmCalls, fallback) => {
|
|
|
47
52
|
(llmCalls.length > 0 ? (llmCalls[llmCalls.length - 1]?.model ?? null) : null) ??
|
|
48
53
|
fallback);
|
|
49
54
|
};
|
|
55
|
+
const buildModelMetaFromAttempt = (attempt) => {
|
|
56
|
+
if (attempt.transport === 'cli') {
|
|
57
|
+
return { provider: 'cli', canonical: attempt.userModelId };
|
|
58
|
+
}
|
|
59
|
+
const parsed = parseGatewayStyleModelId(attempt.llmModelId ?? attempt.userModelId);
|
|
60
|
+
const canonical = attempt.userModelId.toLowerCase().startsWith('openrouter/')
|
|
61
|
+
? attempt.userModelId
|
|
62
|
+
: parsed.canonical;
|
|
63
|
+
return { provider: parsed.provider, canonical };
|
|
64
|
+
};
|
|
50
65
|
export async function outputExtractedUrl({ ctx, url, extracted, extractionUi, prompt, effectiveMarkdownMode, transcriptionCostLabel, }) {
|
|
51
|
-
ctx
|
|
66
|
+
const { io, flags, model, hooks } = ctx;
|
|
67
|
+
hooks.clearProgressForStdout();
|
|
52
68
|
const finishLabel = buildExtractFinishLabel({
|
|
53
69
|
extracted: { diagnostics: extracted.diagnostics },
|
|
54
|
-
format:
|
|
70
|
+
format: flags.format,
|
|
55
71
|
markdownMode: effectiveMarkdownMode,
|
|
56
|
-
hasMarkdownLlmCall:
|
|
72
|
+
hasMarkdownLlmCall: model.llmCalls.some((call) => call.purpose === 'markdown'),
|
|
57
73
|
});
|
|
58
|
-
const finishModel = pickModelForFinishLine(
|
|
59
|
-
if (
|
|
60
|
-
const finishReport =
|
|
74
|
+
const finishModel = pickModelForFinishLine(model.llmCalls, null);
|
|
75
|
+
if (flags.json) {
|
|
76
|
+
const finishReport = flags.shouldComputeReport ? await hooks.buildReport() : null;
|
|
61
77
|
const payload = {
|
|
62
78
|
input: {
|
|
63
79
|
kind: 'url',
|
|
64
80
|
url,
|
|
65
|
-
timeoutMs:
|
|
66
|
-
youtube:
|
|
67
|
-
firecrawl:
|
|
68
|
-
format:
|
|
81
|
+
timeoutMs: flags.timeoutMs,
|
|
82
|
+
youtube: flags.youtubeMode,
|
|
83
|
+
firecrawl: flags.firecrawlMode,
|
|
84
|
+
format: flags.format,
|
|
69
85
|
markdown: effectiveMarkdownMode,
|
|
70
|
-
length:
|
|
71
|
-
? { kind: 'preset', preset:
|
|
72
|
-
: { kind: 'chars', maxCharacters:
|
|
73
|
-
maxOutputTokens:
|
|
74
|
-
model:
|
|
75
|
-
language: formatOutputLanguageForJson(
|
|
86
|
+
length: flags.lengthArg.kind === 'preset'
|
|
87
|
+
? { kind: 'preset', preset: flags.lengthArg.preset }
|
|
88
|
+
: { kind: 'chars', maxCharacters: flags.lengthArg.maxCharacters },
|
|
89
|
+
maxOutputTokens: flags.maxOutputTokensArg,
|
|
90
|
+
model: model.requestedModelLabel,
|
|
91
|
+
language: formatOutputLanguageForJson(flags.outputLanguage),
|
|
76
92
|
},
|
|
77
93
|
env: {
|
|
78
|
-
hasXaiKey: Boolean(
|
|
79
|
-
hasOpenAIKey: Boolean(
|
|
80
|
-
hasOpenRouterKey: Boolean(
|
|
81
|
-
hasApifyToken: Boolean(
|
|
82
|
-
hasFirecrawlKey:
|
|
83
|
-
hasGoogleKey:
|
|
84
|
-
hasAnthropicKey:
|
|
94
|
+
hasXaiKey: Boolean(model.apiStatus.xaiApiKey),
|
|
95
|
+
hasOpenAIKey: Boolean(model.apiStatus.apiKey),
|
|
96
|
+
hasOpenRouterKey: Boolean(model.apiStatus.openrouterApiKey),
|
|
97
|
+
hasApifyToken: Boolean(model.apiStatus.apifyToken),
|
|
98
|
+
hasFirecrawlKey: model.apiStatus.firecrawlConfigured,
|
|
99
|
+
hasGoogleKey: model.apiStatus.googleConfigured,
|
|
100
|
+
hasAnthropicKey: model.apiStatus.anthropicConfigured,
|
|
85
101
|
},
|
|
86
102
|
extracted,
|
|
87
103
|
prompt,
|
|
88
104
|
llm: null,
|
|
89
|
-
metrics:
|
|
105
|
+
metrics: flags.metricsEnabled ? finishReport : null,
|
|
90
106
|
summary: null,
|
|
91
107
|
};
|
|
92
|
-
|
|
93
|
-
if (
|
|
94
|
-
const costUsd = await
|
|
108
|
+
io.stdout.write(`${JSON.stringify(payload, null, 2)}\n`);
|
|
109
|
+
if (flags.metricsEnabled && finishReport) {
|
|
110
|
+
const costUsd = await hooks.estimateCostUsd();
|
|
95
111
|
writeFinishLine({
|
|
96
|
-
stderr:
|
|
97
|
-
elapsedMs: Date.now() -
|
|
112
|
+
stderr: io.stderr,
|
|
113
|
+
elapsedMs: Date.now() - flags.runStartedAtMs,
|
|
98
114
|
label: finishLabel,
|
|
99
115
|
model: finishModel,
|
|
100
116
|
report: finishReport,
|
|
101
117
|
costUsd,
|
|
102
|
-
detailed:
|
|
118
|
+
detailed: flags.metricsDetailed,
|
|
103
119
|
extraParts: buildFinishExtras({
|
|
104
120
|
extracted,
|
|
105
|
-
metricsDetailed:
|
|
121
|
+
metricsDetailed: flags.metricsDetailed,
|
|
106
122
|
transcriptionCostLabel,
|
|
107
123
|
}),
|
|
108
|
-
color:
|
|
124
|
+
color: flags.verboseColor,
|
|
109
125
|
});
|
|
110
126
|
}
|
|
111
127
|
return;
|
|
112
128
|
}
|
|
113
|
-
const renderedExtract =
|
|
129
|
+
const renderedExtract = flags.format === 'markdown' && !flags.plain && isRichTty(io.stdout)
|
|
114
130
|
? renderMarkdownAnsi(prepareMarkdownForTerminal(extracted.content), {
|
|
115
|
-
width: markdownRenderWidth(
|
|
131
|
+
width: markdownRenderWidth(io.stdout, io.env),
|
|
116
132
|
wrap: true,
|
|
117
|
-
color: supportsColor(
|
|
133
|
+
color: supportsColor(io.stdout, io.envForRun),
|
|
118
134
|
hyperlinks: true,
|
|
119
135
|
})
|
|
120
136
|
: extracted.content;
|
|
121
|
-
if (
|
|
122
|
-
|
|
137
|
+
if (flags.format === 'markdown' && !flags.plain && isRichTty(io.stdout)) {
|
|
138
|
+
io.stdout.write(`\n${renderedExtract.replace(/^\n+/, '')}`);
|
|
123
139
|
}
|
|
124
140
|
else {
|
|
125
|
-
|
|
141
|
+
io.stdout.write(renderedExtract);
|
|
126
142
|
}
|
|
127
143
|
if (!renderedExtract.endsWith('\n')) {
|
|
128
|
-
|
|
144
|
+
io.stdout.write('\n');
|
|
129
145
|
}
|
|
130
|
-
|
|
131
|
-
const report =
|
|
132
|
-
if (
|
|
133
|
-
const costUsd = await
|
|
146
|
+
hooks.writeViaFooter(extractionUi.footerParts);
|
|
147
|
+
const report = flags.shouldComputeReport ? await hooks.buildReport() : null;
|
|
148
|
+
if (flags.metricsEnabled && report) {
|
|
149
|
+
const costUsd = await hooks.estimateCostUsd();
|
|
134
150
|
writeFinishLine({
|
|
135
|
-
stderr:
|
|
136
|
-
elapsedMs: Date.now() -
|
|
151
|
+
stderr: io.stderr,
|
|
152
|
+
elapsedMs: Date.now() - flags.runStartedAtMs,
|
|
137
153
|
label: finishLabel,
|
|
138
154
|
model: finishModel,
|
|
139
155
|
report,
|
|
140
156
|
costUsd,
|
|
141
|
-
detailed:
|
|
157
|
+
detailed: flags.metricsDetailed,
|
|
142
158
|
extraParts: buildFinishExtras({
|
|
143
159
|
extracted,
|
|
144
|
-
metricsDetailed:
|
|
160
|
+
metricsDetailed: flags.metricsDetailed,
|
|
145
161
|
transcriptionCostLabel,
|
|
146
162
|
}),
|
|
147
|
-
color:
|
|
163
|
+
color: flags.verboseColor,
|
|
148
164
|
});
|
|
149
165
|
}
|
|
150
166
|
}
|
|
151
167
|
export async function summarizeExtractedUrl({ ctx, url, extracted, extractionUi, prompt, effectiveMarkdownMode, transcriptionCostLabel, onModelChosen, }) {
|
|
168
|
+
const { io, flags, model, cache: cacheState, hooks } = ctx;
|
|
152
169
|
const promptTokens = countTokens(prompt);
|
|
153
170
|
const kindForAuto = extracted.siteName === 'YouTube' ? 'youtube' : 'website';
|
|
154
171
|
const attempts = await (async () => {
|
|
155
|
-
if (
|
|
156
|
-
const catalog = await
|
|
172
|
+
if (model.isFallbackModel) {
|
|
173
|
+
const catalog = await model.getLiteLlmCatalog();
|
|
157
174
|
const list = buildAutoModelAttempts({
|
|
158
175
|
kind: kindForAuto,
|
|
159
176
|
promptTokens,
|
|
160
|
-
desiredOutputTokens:
|
|
177
|
+
desiredOutputTokens: model.desiredOutputTokens,
|
|
161
178
|
requiresVideoUnderstanding: false,
|
|
162
|
-
env:
|
|
163
|
-
config:
|
|
179
|
+
env: model.envForAuto,
|
|
180
|
+
config: model.configForModelSelection,
|
|
164
181
|
catalog,
|
|
165
182
|
openrouterProvidersFromEnv: null,
|
|
166
|
-
cliAvailability:
|
|
183
|
+
cliAvailability: model.cliAvailability,
|
|
167
184
|
});
|
|
168
|
-
if (
|
|
185
|
+
if (flags.verbose) {
|
|
169
186
|
for (const attempt of list.slice(0, 8)) {
|
|
170
|
-
writeVerbose(
|
|
187
|
+
writeVerbose(io.stderr, flags.verbose, `auto candidate ${attempt.debug}`, flags.verboseColor);
|
|
171
188
|
}
|
|
172
189
|
}
|
|
173
190
|
return list.map((attempt) => {
|
|
174
191
|
if (attempt.transport !== 'cli')
|
|
175
|
-
return
|
|
192
|
+
return model.summaryEngine.applyZaiOverrides(attempt);
|
|
176
193
|
const parsed = parseCliUserModelId(attempt.userModelId);
|
|
177
194
|
return { ...attempt, cliProvider: parsed.provider, cliModel: parsed.model };
|
|
178
195
|
});
|
|
179
196
|
}
|
|
180
197
|
/* v8 ignore next */
|
|
181
|
-
if (!
|
|
198
|
+
if (!model.fixedModelSpec) {
|
|
182
199
|
throw new Error('Internal error: missing fixed model spec');
|
|
183
200
|
}
|
|
184
|
-
if (
|
|
201
|
+
if (model.fixedModelSpec.transport === 'cli') {
|
|
185
202
|
return [
|
|
186
203
|
{
|
|
187
204
|
transport: 'cli',
|
|
188
|
-
userModelId:
|
|
205
|
+
userModelId: model.fixedModelSpec.userModelId,
|
|
189
206
|
llmModelId: null,
|
|
190
|
-
cliProvider:
|
|
191
|
-
cliModel:
|
|
207
|
+
cliProvider: model.fixedModelSpec.cliProvider,
|
|
208
|
+
cliModel: model.fixedModelSpec.cliModel,
|
|
192
209
|
openrouterProviders: null,
|
|
193
210
|
forceOpenRouter: false,
|
|
194
|
-
requiredEnv:
|
|
211
|
+
requiredEnv: model.fixedModelSpec.requiredEnv,
|
|
195
212
|
},
|
|
196
213
|
];
|
|
197
214
|
}
|
|
198
|
-
const openaiOverrides =
|
|
215
|
+
const openaiOverrides = model.fixedModelSpec.requiredEnv === 'Z_AI_API_KEY'
|
|
199
216
|
? {
|
|
200
|
-
openaiApiKeyOverride:
|
|
201
|
-
openaiBaseUrlOverride:
|
|
217
|
+
openaiApiKeyOverride: model.apiStatus.zaiApiKey,
|
|
218
|
+
openaiBaseUrlOverride: model.apiStatus.zaiBaseUrl,
|
|
202
219
|
forceChatCompletions: true,
|
|
203
220
|
}
|
|
204
221
|
: {};
|
|
205
222
|
return [
|
|
206
223
|
{
|
|
207
|
-
transport:
|
|
208
|
-
userModelId:
|
|
209
|
-
llmModelId:
|
|
210
|
-
openrouterProviders:
|
|
211
|
-
forceOpenRouter:
|
|
212
|
-
requiredEnv:
|
|
224
|
+
transport: model.fixedModelSpec.transport === 'openrouter' ? 'openrouter' : 'native',
|
|
225
|
+
userModelId: model.fixedModelSpec.userModelId,
|
|
226
|
+
llmModelId: model.fixedModelSpec.llmModelId,
|
|
227
|
+
openrouterProviders: model.fixedModelSpec.openrouterProviders,
|
|
228
|
+
forceOpenRouter: model.fixedModelSpec.forceOpenRouter,
|
|
229
|
+
requiredEnv: model.fixedModelSpec.requiredEnv,
|
|
213
230
|
...openaiOverrides,
|
|
214
231
|
},
|
|
215
232
|
];
|
|
216
233
|
})();
|
|
217
|
-
const
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
234
|
+
const cacheStore = cacheState.mode === 'default' ? cacheState.store : null;
|
|
235
|
+
const contentHash = cacheStore ? hashString(normalizeContentForHash(extracted.content)) : null;
|
|
236
|
+
const promptHash = cacheStore ? buildPromptHash(prompt) : null;
|
|
237
|
+
const lengthKey = buildLengthKey(flags.lengthArg);
|
|
238
|
+
const languageKey = buildLanguageKey(flags.outputLanguage);
|
|
239
|
+
let summaryResult = null;
|
|
240
|
+
let usedAttempt = null;
|
|
241
|
+
let summaryFromCache = false;
|
|
242
|
+
let cacheChecked = false;
|
|
243
|
+
if (cacheStore && contentHash && promptHash) {
|
|
244
|
+
cacheChecked = true;
|
|
245
|
+
for (const attempt of attempts) {
|
|
246
|
+
if (!model.summaryEngine.envHasKeyFor(attempt.requiredEnv))
|
|
247
|
+
continue;
|
|
248
|
+
const key = buildSummaryCacheKey({
|
|
249
|
+
contentHash,
|
|
250
|
+
promptHash,
|
|
251
|
+
model: attempt.userModelId,
|
|
252
|
+
lengthKey,
|
|
253
|
+
languageKey,
|
|
254
|
+
});
|
|
255
|
+
const cached = cacheStore.getText('summary', key);
|
|
256
|
+
if (!cached)
|
|
257
|
+
continue;
|
|
258
|
+
writeVerbose(io.stderr, flags.verbose, 'cache hit summary', flags.verboseColor);
|
|
259
|
+
onModelChosen?.(attempt.userModelId);
|
|
260
|
+
summaryResult = {
|
|
261
|
+
summary: cached,
|
|
262
|
+
summaryAlreadyPrinted: false,
|
|
263
|
+
modelMeta: buildModelMetaFromAttempt(attempt),
|
|
264
|
+
maxOutputTokensForCall: null,
|
|
265
|
+
};
|
|
266
|
+
usedAttempt = attempt;
|
|
267
|
+
summaryFromCache = true;
|
|
268
|
+
break;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
if (cacheChecked && !summaryFromCache) {
|
|
272
|
+
writeVerbose(io.stderr, flags.verbose, 'cache miss summary', flags.verboseColor);
|
|
273
|
+
}
|
|
274
|
+
ctx.hooks.onSummaryCached?.(summaryFromCache);
|
|
275
|
+
let lastError = null;
|
|
276
|
+
let missingRequiredEnvs = new Set();
|
|
277
|
+
let sawOpenRouterNoAllowedProviders = false;
|
|
278
|
+
if (!summaryResult || !usedAttempt) {
|
|
279
|
+
const attemptOutcome = await runModelAttempts({
|
|
280
|
+
attempts,
|
|
281
|
+
isFallbackModel: model.isFallbackModel,
|
|
282
|
+
isNamedModelSelection: model.isNamedModelSelection,
|
|
283
|
+
envHasKeyFor: model.summaryEngine.envHasKeyFor,
|
|
284
|
+
formatMissingModelError: model.summaryEngine.formatMissingModelError,
|
|
285
|
+
onAutoSkip: (attempt) => {
|
|
286
|
+
writeVerbose(io.stderr, flags.verbose, `auto skip ${attempt.userModelId}: missing ${attempt.requiredEnv}`, flags.verboseColor);
|
|
287
|
+
},
|
|
288
|
+
onAutoFailure: (attempt, error) => {
|
|
289
|
+
writeVerbose(io.stderr, flags.verbose, `auto failed ${attempt.userModelId}: ${error instanceof Error ? error.message : String(error)}`, flags.verboseColor);
|
|
290
|
+
},
|
|
291
|
+
onFixedModelError: (_attempt, error) => {
|
|
292
|
+
throw error;
|
|
293
|
+
},
|
|
294
|
+
runAttempt: (attempt) => model.summaryEngine.runSummaryAttempt({
|
|
295
|
+
attempt,
|
|
296
|
+
prompt,
|
|
297
|
+
allowStreaming: flags.streamingEnabled,
|
|
298
|
+
onModelChosen: onModelChosen ?? null,
|
|
299
|
+
}),
|
|
300
|
+
});
|
|
301
|
+
summaryResult = attemptOutcome.result;
|
|
302
|
+
usedAttempt = attemptOutcome.usedAttempt;
|
|
303
|
+
lastError = attemptOutcome.lastError;
|
|
304
|
+
missingRequiredEnvs = attemptOutcome.missingRequiredEnvs;
|
|
305
|
+
sawOpenRouterNoAllowedProviders = attemptOutcome.sawOpenRouterNoAllowedProviders;
|
|
306
|
+
}
|
|
242
307
|
if (!summaryResult || !usedAttempt) {
|
|
243
308
|
// Auto mode: surface raw extracted content when no model can run.
|
|
244
309
|
const withFreeTip = (message) => {
|
|
245
|
-
if (!
|
|
310
|
+
if (!model.isNamedModelSelection || !model.wantsFreeNamedModel)
|
|
246
311
|
return message;
|
|
247
312
|
return (`${message}\n` +
|
|
248
313
|
`Tip: run "summarize refresh-free" to refresh the free model candidates (writes ~/.summarize/config.json).`);
|
|
249
314
|
};
|
|
250
|
-
if (
|
|
315
|
+
if (model.isNamedModelSelection) {
|
|
251
316
|
if (lastError === null && missingRequiredEnvs.size > 0) {
|
|
252
|
-
throw new Error(withFreeTip(`Missing ${Array.from(missingRequiredEnvs).sort().join(', ')} for --model ${
|
|
317
|
+
throw new Error(withFreeTip(`Missing ${Array.from(missingRequiredEnvs).sort().join(', ')} for --model ${model.requestedModelInput}.`));
|
|
253
318
|
}
|
|
254
319
|
if (lastError instanceof Error) {
|
|
255
320
|
if (sawOpenRouterNoAllowedProviders) {
|
|
256
321
|
const message = await buildOpenRouterNoAllowedProvidersMessage({
|
|
257
322
|
attempts,
|
|
258
|
-
fetchImpl:
|
|
259
|
-
timeoutMs:
|
|
323
|
+
fetchImpl: io.fetch,
|
|
324
|
+
timeoutMs: flags.timeoutMs,
|
|
260
325
|
});
|
|
261
326
|
throw new Error(withFreeTip(message), { cause: lastError });
|
|
262
327
|
}
|
|
263
328
|
throw new Error(withFreeTip(lastError.message), { cause: lastError });
|
|
264
329
|
}
|
|
265
|
-
throw new Error(withFreeTip(`No model available for --model ${
|
|
330
|
+
throw new Error(withFreeTip(`No model available for --model ${model.requestedModelInput}`));
|
|
266
331
|
}
|
|
267
|
-
|
|
268
|
-
if (
|
|
269
|
-
const finishReport =
|
|
270
|
-
const finishModel = pickModelForFinishLine(
|
|
332
|
+
hooks.clearProgressForStdout();
|
|
333
|
+
if (flags.json) {
|
|
334
|
+
const finishReport = flags.shouldComputeReport ? await hooks.buildReport() : null;
|
|
335
|
+
const finishModel = pickModelForFinishLine(model.llmCalls, null);
|
|
271
336
|
const payload = {
|
|
272
337
|
input: {
|
|
273
338
|
kind: 'url',
|
|
274
339
|
url,
|
|
275
|
-
timeoutMs:
|
|
276
|
-
youtube:
|
|
277
|
-
firecrawl:
|
|
278
|
-
format:
|
|
340
|
+
timeoutMs: flags.timeoutMs,
|
|
341
|
+
youtube: flags.youtubeMode,
|
|
342
|
+
firecrawl: flags.firecrawlMode,
|
|
343
|
+
format: flags.format,
|
|
279
344
|
markdown: effectiveMarkdownMode,
|
|
280
|
-
length:
|
|
281
|
-
? { kind: 'preset', preset:
|
|
282
|
-
: { kind: 'chars', maxCharacters:
|
|
283
|
-
maxOutputTokens:
|
|
284
|
-
model:
|
|
285
|
-
language: formatOutputLanguageForJson(
|
|
345
|
+
length: flags.lengthArg.kind === 'preset'
|
|
346
|
+
? { kind: 'preset', preset: flags.lengthArg.preset }
|
|
347
|
+
: { kind: 'chars', maxCharacters: flags.lengthArg.maxCharacters },
|
|
348
|
+
maxOutputTokens: flags.maxOutputTokensArg,
|
|
349
|
+
model: model.requestedModelLabel,
|
|
350
|
+
language: formatOutputLanguageForJson(flags.outputLanguage),
|
|
286
351
|
},
|
|
287
352
|
env: {
|
|
288
|
-
hasXaiKey: Boolean(
|
|
289
|
-
hasOpenAIKey: Boolean(
|
|
290
|
-
hasOpenRouterKey: Boolean(
|
|
291
|
-
hasApifyToken: Boolean(
|
|
292
|
-
hasFirecrawlKey:
|
|
293
|
-
hasGoogleKey:
|
|
294
|
-
hasAnthropicKey:
|
|
353
|
+
hasXaiKey: Boolean(model.apiStatus.xaiApiKey),
|
|
354
|
+
hasOpenAIKey: Boolean(model.apiStatus.apiKey),
|
|
355
|
+
hasOpenRouterKey: Boolean(model.apiStatus.openrouterApiKey),
|
|
356
|
+
hasApifyToken: Boolean(model.apiStatus.apifyToken),
|
|
357
|
+
hasFirecrawlKey: model.apiStatus.firecrawlConfigured,
|
|
358
|
+
hasGoogleKey: model.apiStatus.googleConfigured,
|
|
359
|
+
hasAnthropicKey: model.apiStatus.anthropicConfigured,
|
|
295
360
|
},
|
|
296
361
|
extracted,
|
|
297
362
|
prompt,
|
|
298
363
|
llm: null,
|
|
299
|
-
metrics:
|
|
364
|
+
metrics: flags.metricsEnabled ? finishReport : null,
|
|
300
365
|
summary: extracted.content,
|
|
301
366
|
};
|
|
302
|
-
|
|
303
|
-
if (
|
|
304
|
-
const costUsd = await
|
|
367
|
+
io.stdout.write(`${JSON.stringify(payload, null, 2)}\n`);
|
|
368
|
+
if (flags.metricsEnabled && finishReport) {
|
|
369
|
+
const costUsd = await hooks.estimateCostUsd();
|
|
305
370
|
writeFinishLine({
|
|
306
|
-
stderr:
|
|
307
|
-
elapsedMs: Date.now() -
|
|
371
|
+
stderr: io.stderr,
|
|
372
|
+
elapsedMs: Date.now() - flags.runStartedAtMs,
|
|
308
373
|
label: extractionUi.finishSourceLabel,
|
|
309
374
|
model: finishModel,
|
|
310
375
|
report: finishReport,
|
|
311
376
|
costUsd,
|
|
312
|
-
detailed:
|
|
377
|
+
detailed: flags.metricsDetailed,
|
|
313
378
|
extraParts: buildFinishExtras({
|
|
314
379
|
extracted,
|
|
315
|
-
metricsDetailed:
|
|
380
|
+
metricsDetailed: flags.metricsDetailed,
|
|
316
381
|
transcriptionCostLabel,
|
|
317
382
|
}),
|
|
318
|
-
color:
|
|
383
|
+
color: flags.verboseColor,
|
|
319
384
|
});
|
|
320
385
|
}
|
|
321
386
|
return;
|
|
322
387
|
}
|
|
323
|
-
|
|
388
|
+
io.stdout.write(`${extracted.content}\n`);
|
|
324
389
|
if (extractionUi.footerParts.length > 0) {
|
|
325
|
-
|
|
390
|
+
hooks.writeViaFooter([...extractionUi.footerParts, 'no model']);
|
|
326
391
|
}
|
|
327
|
-
if (lastError instanceof Error &&
|
|
328
|
-
writeVerbose(
|
|
392
|
+
if (lastError instanceof Error && flags.verbose) {
|
|
393
|
+
writeVerbose(io.stderr, flags.verbose, `auto failed all models: ${lastError.message}`, flags.verboseColor);
|
|
329
394
|
}
|
|
330
395
|
return;
|
|
331
396
|
}
|
|
397
|
+
if (!summaryFromCache && cacheStore && contentHash && promptHash) {
|
|
398
|
+
const key = buildSummaryCacheKey({
|
|
399
|
+
contentHash,
|
|
400
|
+
promptHash,
|
|
401
|
+
model: usedAttempt.userModelId,
|
|
402
|
+
lengthKey,
|
|
403
|
+
languageKey,
|
|
404
|
+
});
|
|
405
|
+
cacheStore.setText('summary', key, summaryResult.summary, cacheState.ttlMs);
|
|
406
|
+
writeVerbose(io.stderr, flags.verbose, 'cache write summary', flags.verboseColor);
|
|
407
|
+
}
|
|
332
408
|
const { summary, summaryAlreadyPrinted, modelMeta, maxOutputTokensForCall } = summaryResult;
|
|
333
|
-
if (
|
|
334
|
-
const finishReport =
|
|
409
|
+
if (flags.json) {
|
|
410
|
+
const finishReport = flags.shouldComputeReport ? await hooks.buildReport() : null;
|
|
335
411
|
const payload = {
|
|
336
412
|
input: {
|
|
337
413
|
kind: 'url',
|
|
338
414
|
url,
|
|
339
|
-
timeoutMs:
|
|
340
|
-
youtube:
|
|
341
|
-
firecrawl:
|
|
342
|
-
format:
|
|
415
|
+
timeoutMs: flags.timeoutMs,
|
|
416
|
+
youtube: flags.youtubeMode,
|
|
417
|
+
firecrawl: flags.firecrawlMode,
|
|
418
|
+
format: flags.format,
|
|
343
419
|
markdown: effectiveMarkdownMode,
|
|
344
|
-
length:
|
|
345
|
-
? { kind: 'preset', preset:
|
|
346
|
-
: { kind: 'chars', maxCharacters:
|
|
347
|
-
maxOutputTokens:
|
|
348
|
-
model:
|
|
349
|
-
language: formatOutputLanguageForJson(
|
|
420
|
+
length: flags.lengthArg.kind === 'preset'
|
|
421
|
+
? { kind: 'preset', preset: flags.lengthArg.preset }
|
|
422
|
+
: { kind: 'chars', maxCharacters: flags.lengthArg.maxCharacters },
|
|
423
|
+
maxOutputTokens: flags.maxOutputTokensArg,
|
|
424
|
+
model: model.requestedModelLabel,
|
|
425
|
+
language: formatOutputLanguageForJson(flags.outputLanguage),
|
|
350
426
|
},
|
|
351
427
|
env: {
|
|
352
|
-
hasXaiKey: Boolean(
|
|
353
|
-
hasOpenAIKey: Boolean(
|
|
354
|
-
hasOpenRouterKey: Boolean(
|
|
355
|
-
hasApifyToken: Boolean(
|
|
356
|
-
hasFirecrawlKey:
|
|
357
|
-
hasGoogleKey:
|
|
358
|
-
hasAnthropicKey:
|
|
428
|
+
hasXaiKey: Boolean(model.apiStatus.xaiApiKey),
|
|
429
|
+
hasOpenAIKey: Boolean(model.apiStatus.apiKey),
|
|
430
|
+
hasOpenRouterKey: Boolean(model.apiStatus.openrouterApiKey),
|
|
431
|
+
hasApifyToken: Boolean(model.apiStatus.apifyToken),
|
|
432
|
+
hasFirecrawlKey: model.apiStatus.firecrawlConfigured,
|
|
433
|
+
hasGoogleKey: model.apiStatus.googleConfigured,
|
|
434
|
+
hasAnthropicKey: model.apiStatus.anthropicConfigured,
|
|
359
435
|
},
|
|
360
436
|
extracted,
|
|
361
437
|
prompt,
|
|
@@ -365,69 +441,71 @@ export async function summarizeExtractedUrl({ ctx, url, extracted, extractionUi,
|
|
|
365
441
|
maxCompletionTokens: maxOutputTokensForCall,
|
|
366
442
|
strategy: 'single',
|
|
367
443
|
},
|
|
368
|
-
metrics:
|
|
444
|
+
metrics: flags.metricsEnabled ? finishReport : null,
|
|
369
445
|
summary,
|
|
370
446
|
};
|
|
371
|
-
|
|
372
|
-
if (
|
|
373
|
-
const costUsd = await
|
|
447
|
+
io.stdout.write(`${JSON.stringify(payload, null, 2)}\n`);
|
|
448
|
+
if (flags.metricsEnabled && finishReport) {
|
|
449
|
+
const costUsd = await hooks.estimateCostUsd();
|
|
374
450
|
writeFinishLine({
|
|
375
|
-
stderr:
|
|
376
|
-
elapsedMs: Date.now() -
|
|
451
|
+
stderr: io.stderr,
|
|
452
|
+
elapsedMs: Date.now() - flags.runStartedAtMs,
|
|
453
|
+
elapsedLabel: summaryFromCache ? 'Cached' : null,
|
|
377
454
|
label: extractionUi.finishSourceLabel,
|
|
378
455
|
model: usedAttempt.userModelId,
|
|
379
456
|
report: finishReport,
|
|
380
457
|
costUsd,
|
|
381
|
-
detailed:
|
|
458
|
+
detailed: flags.metricsDetailed,
|
|
382
459
|
extraParts: buildFinishExtras({
|
|
383
460
|
extracted,
|
|
384
|
-
metricsDetailed:
|
|
461
|
+
metricsDetailed: flags.metricsDetailed,
|
|
385
462
|
transcriptionCostLabel,
|
|
386
463
|
}),
|
|
387
|
-
color:
|
|
464
|
+
color: flags.verboseColor,
|
|
388
465
|
});
|
|
389
466
|
}
|
|
390
467
|
return;
|
|
391
468
|
}
|
|
392
469
|
if (!summaryAlreadyPrinted) {
|
|
393
|
-
|
|
394
|
-
const rendered = !
|
|
470
|
+
hooks.clearProgressForStdout();
|
|
471
|
+
const rendered = !flags.plain && isRichTty(io.stdout)
|
|
395
472
|
? renderMarkdownAnsi(prepareMarkdownForTerminal(summary), {
|
|
396
|
-
width: markdownRenderWidth(
|
|
473
|
+
width: markdownRenderWidth(io.stdout, io.env),
|
|
397
474
|
wrap: true,
|
|
398
|
-
color: supportsColor(
|
|
475
|
+
color: supportsColor(io.stdout, io.envForRun),
|
|
399
476
|
hyperlinks: true,
|
|
400
477
|
})
|
|
401
478
|
: summary;
|
|
402
|
-
if (!
|
|
403
|
-
|
|
479
|
+
if (!flags.plain && isRichTty(io.stdout)) {
|
|
480
|
+
io.stdout.write(`\n${rendered.replace(/^\n+/, '')}`);
|
|
404
481
|
}
|
|
405
482
|
else {
|
|
406
|
-
if (isRichTty(
|
|
407
|
-
|
|
408
|
-
|
|
483
|
+
if (isRichTty(io.stdout))
|
|
484
|
+
io.stdout.write('\n');
|
|
485
|
+
io.stdout.write(rendered.replace(/^\n+/, ''));
|
|
409
486
|
}
|
|
410
487
|
if (!rendered.endsWith('\n')) {
|
|
411
|
-
|
|
488
|
+
io.stdout.write('\n');
|
|
412
489
|
}
|
|
413
490
|
}
|
|
414
|
-
const report =
|
|
415
|
-
if (
|
|
416
|
-
const costUsd = await
|
|
491
|
+
const report = flags.shouldComputeReport ? await hooks.buildReport() : null;
|
|
492
|
+
if (flags.metricsEnabled && report) {
|
|
493
|
+
const costUsd = await hooks.estimateCostUsd();
|
|
417
494
|
writeFinishLine({
|
|
418
|
-
stderr:
|
|
419
|
-
elapsedMs: Date.now() -
|
|
495
|
+
stderr: io.stderr,
|
|
496
|
+
elapsedMs: Date.now() - flags.runStartedAtMs,
|
|
497
|
+
elapsedLabel: summaryFromCache ? 'Cached' : null,
|
|
420
498
|
label: extractionUi.finishSourceLabel,
|
|
421
499
|
model: modelMeta.canonical,
|
|
422
500
|
report,
|
|
423
501
|
costUsd,
|
|
424
|
-
detailed:
|
|
502
|
+
detailed: flags.metricsDetailed,
|
|
425
503
|
extraParts: buildFinishExtras({
|
|
426
504
|
extracted,
|
|
427
|
-
metricsDetailed:
|
|
505
|
+
metricsDetailed: flags.metricsDetailed,
|
|
428
506
|
transcriptionCostLabel,
|
|
429
507
|
}),
|
|
430
|
-
color:
|
|
508
|
+
color: flags.verboseColor,
|
|
431
509
|
});
|
|
432
510
|
}
|
|
433
511
|
}
|