@steel-dev/atlas 0.1.0
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/LICENSE +21 -0
- package/README.md +219 -0
- package/dist/agent.d.ts +34 -0
- package/dist/agent.js +133 -0
- package/dist/async.d.ts +19 -0
- package/dist/async.js +172 -0
- package/dist/atlas.d.ts +19 -0
- package/dist/atlas.js +69 -0
- package/dist/budget.d.ts +64 -0
- package/dist/budget.js +336 -0
- package/dist/checklist.d.ts +115 -0
- package/dist/checklist.js +297 -0
- package/dist/cli.js +38700 -0
- package/dist/config.d.ts +80 -0
- package/dist/config.js +109 -0
- package/dist/context.d.ts +26 -0
- package/dist/context.js +250 -0
- package/dist/custom-tools.d.ts +26 -0
- package/dist/custom-tools.js +33 -0
- package/dist/defaults.d.ts +10 -0
- package/dist/defaults.js +37 -0
- package/dist/economy.d.ts +12 -0
- package/dist/economy.js +6 -0
- package/dist/env.d.ts +1 -0
- package/dist/env.js +8 -0
- package/dist/errors.d.ts +6 -0
- package/dist/errors.js +11 -0
- package/dist/event-hub.d.ts +11 -0
- package/dist/event-hub.js +83 -0
- package/dist/events.d.ts +105 -0
- package/dist/events.js +1 -0
- package/dist/html-extract.d.ts +21 -0
- package/dist/html-extract.js +459 -0
- package/dist/index.d.ts +59 -0
- package/dist/index.js +26 -0
- package/dist/memory.d.ts +2 -0
- package/dist/memory.js +38 -0
- package/dist/model.d.ts +49 -0
- package/dist/model.js +630 -0
- package/dist/orchestrate.d.ts +5 -0
- package/dist/orchestrate.js +277 -0
- package/dist/pdf-extract.d.ts +5 -0
- package/dist/pdf-extract.js +20 -0
- package/dist/prompts.d.ts +2 -0
- package/dist/prompts.js +6 -0
- package/dist/providers/domain/arxiv.d.ts +6 -0
- package/dist/providers/domain/arxiv.js +83 -0
- package/dist/providers/domain/clinicaltrials.d.ts +6 -0
- package/dist/providers/domain/clinicaltrials.js +104 -0
- package/dist/providers/domain/edgar.d.ts +10 -0
- package/dist/providers/domain/edgar.js +92 -0
- package/dist/providers/domain/index.d.ts +14 -0
- package/dist/providers/domain/index.js +7 -0
- package/dist/providers/domain/openalex.d.ts +7 -0
- package/dist/providers/domain/openalex.js +128 -0
- package/dist/providers/domain/pubmed.d.ts +8 -0
- package/dist/providers/domain/pubmed.js +123 -0
- package/dist/providers/domain/semantic-scholar.d.ts +6 -0
- package/dist/providers/domain/semantic-scholar.js +112 -0
- package/dist/providers/domain/shared.d.ts +12 -0
- package/dist/providers/domain/shared.js +39 -0
- package/dist/providers/domain/wikipedia.d.ts +6 -0
- package/dist/providers/domain/wikipedia.js +71 -0
- package/dist/providers/exa-agent.d.ts +9 -0
- package/dist/providers/exa-agent.js +67 -0
- package/dist/providers/fetch.d.ts +66 -0
- package/dist/providers/fetch.js +675 -0
- package/dist/providers/parallel-agent.d.ts +11 -0
- package/dist/providers/parallel-agent.js +100 -0
- package/dist/providers/perplexity-agent.d.ts +17 -0
- package/dist/providers/perplexity-agent.js +86 -0
- package/dist/providers/search.d.ts +65 -0
- package/dist/providers/search.js +433 -0
- package/dist/providers/store.d.ts +48 -0
- package/dist/providers/store.js +217 -0
- package/dist/researcher.d.ts +20 -0
- package/dist/researcher.js +3 -0
- package/dist/robots.d.ts +16 -0
- package/dist/robots.js +146 -0
- package/dist/roles.d.ts +6 -0
- package/dist/roles.js +4 -0
- package/dist/run.d.ts +65 -0
- package/dist/run.js +371 -0
- package/dist/safe-dispatcher.d.ts +16 -0
- package/dist/safe-dispatcher.js +32 -0
- package/dist/safety.d.ts +23 -0
- package/dist/safety.js +206 -0
- package/dist/sandbox.d.ts +22 -0
- package/dist/sandbox.js +228 -0
- package/dist/search-normalize.d.ts +2 -0
- package/dist/search-normalize.js +13 -0
- package/dist/source-documents.d.ts +77 -0
- package/dist/source-documents.js +421 -0
- package/dist/sources.d.ts +57 -0
- package/dist/sources.js +1 -0
- package/dist/spine.d.ts +19 -0
- package/dist/spine.js +722 -0
- package/dist/state.d.ts +90 -0
- package/dist/state.js +27 -0
- package/dist/structured.d.ts +7 -0
- package/dist/structured.js +18 -0
- package/dist/tools.d.ts +33 -0
- package/dist/tools.js +1187 -0
- package/dist/trace-digest.d.ts +11 -0
- package/dist/trace-digest.js +309 -0
- package/dist/trace.d.ts +225 -0
- package/dist/trace.js +278 -0
- package/dist/trail.d.ts +15 -0
- package/dist/trail.js +74 -0
- package/dist/url.d.ts +1 -0
- package/dist/url.js +25 -0
- package/package.json +107 -0
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import type { PricingTable } from "./budget.js";
|
|
2
|
+
import type { ResearchTool } from "./custom-tools.js";
|
|
3
|
+
import type { ModelRole, ResolvedModel } from "./model.js";
|
|
4
|
+
import type { FetchProvider } from "./providers/fetch.js";
|
|
5
|
+
import type { SearchProvider } from "./providers/search.js";
|
|
6
|
+
import type { RunStore } from "./providers/store.js";
|
|
7
|
+
import type { Researcher } from "./researcher.js";
|
|
8
|
+
import type { SafetyPolicy } from "./safety.js";
|
|
9
|
+
export type Effort = "fast" | "balanced" | "deep" | "max";
|
|
10
|
+
export type TraceMode = "off" | "spans" | "full";
|
|
11
|
+
export interface EffortEnvelope {
|
|
12
|
+
budgetUSD: number;
|
|
13
|
+
maxSources: number;
|
|
14
|
+
maxTokens: number;
|
|
15
|
+
maxTurns: number;
|
|
16
|
+
maxReportTokens: number;
|
|
17
|
+
maxDurationMs: number;
|
|
18
|
+
}
|
|
19
|
+
export declare const EFFORT_ENVELOPES: Record<Effort, EffortEnvelope>;
|
|
20
|
+
export interface Budget {
|
|
21
|
+
maxUSD?: number;
|
|
22
|
+
maxTokens?: number;
|
|
23
|
+
maxDurationMs?: number;
|
|
24
|
+
maxSources?: number;
|
|
25
|
+
}
|
|
26
|
+
export interface SourceFilter {
|
|
27
|
+
includeDomains?: string[];
|
|
28
|
+
excludeDomains?: string[];
|
|
29
|
+
}
|
|
30
|
+
export interface ConcurrencyConfig {
|
|
31
|
+
models?: number;
|
|
32
|
+
io?: number;
|
|
33
|
+
}
|
|
34
|
+
export type SearchConfig = SearchProvider | SearchProvider[] | Record<string, SearchProvider | SearchProvider[]>;
|
|
35
|
+
export interface AtlasConfig {
|
|
36
|
+
model: ResolvedModel;
|
|
37
|
+
models?: {
|
|
38
|
+
research?: ResolvedModel;
|
|
39
|
+
write?: ResolvedModel;
|
|
40
|
+
};
|
|
41
|
+
search?: SearchConfig;
|
|
42
|
+
fetch?: FetchProvider | FetchProvider[];
|
|
43
|
+
effort?: Effort;
|
|
44
|
+
budget?: Budget;
|
|
45
|
+
store?: RunStore;
|
|
46
|
+
pricing?: PricingTable;
|
|
47
|
+
safety?: SafetyPolicy;
|
|
48
|
+
instructions?: string;
|
|
49
|
+
tools?: Record<string, ResearchTool>;
|
|
50
|
+
researchers?: Record<string, Researcher>;
|
|
51
|
+
concurrency?: ConcurrencyConfig;
|
|
52
|
+
trace?: TraceMode;
|
|
53
|
+
}
|
|
54
|
+
export interface ResearchOptions {
|
|
55
|
+
effort?: Effort;
|
|
56
|
+
budget?: Budget;
|
|
57
|
+
sources?: SourceFilter;
|
|
58
|
+
signal?: AbortSignal;
|
|
59
|
+
runId?: string;
|
|
60
|
+
trace?: TraceMode;
|
|
61
|
+
}
|
|
62
|
+
export interface ResolvedRunConfig {
|
|
63
|
+
effort: Effort;
|
|
64
|
+
envelope: EffortEnvelope;
|
|
65
|
+
budgetUSD: number;
|
|
66
|
+
maxTokens: number;
|
|
67
|
+
maxDurationMs?: number | undefined;
|
|
68
|
+
maxSources: number;
|
|
69
|
+
models: Record<ModelRole, ResolvedModel>;
|
|
70
|
+
leadModelId: string;
|
|
71
|
+
pricing: PricingTable;
|
|
72
|
+
safety: SafetyPolicy;
|
|
73
|
+
sourceFilter?: SourceFilter | undefined;
|
|
74
|
+
instructions?: string | undefined;
|
|
75
|
+
maxConcurrentModelCalls: number;
|
|
76
|
+
maxConcurrentIo: number;
|
|
77
|
+
trace: TraceMode;
|
|
78
|
+
researchers: Record<string, Researcher>;
|
|
79
|
+
}
|
|
80
|
+
export declare function resolveRunConfig(config: AtlasConfig, options: ResearchOptions): ResolvedRunConfig;
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { readEnv } from "./env.js";
|
|
2
|
+
import { AtlasError } from "./errors.js";
|
|
3
|
+
export const EFFORT_ENVELOPES = {
|
|
4
|
+
fast: {
|
|
5
|
+
budgetUSD: 0.5,
|
|
6
|
+
maxSources: 15,
|
|
7
|
+
maxTokens: 200_000,
|
|
8
|
+
maxTurns: 30,
|
|
9
|
+
maxReportTokens: 4_096,
|
|
10
|
+
maxDurationMs: 600_000,
|
|
11
|
+
},
|
|
12
|
+
balanced: {
|
|
13
|
+
budgetUSD: 2.5,
|
|
14
|
+
maxSources: 40,
|
|
15
|
+
maxTokens: 1_000_000,
|
|
16
|
+
maxTurns: 60,
|
|
17
|
+
maxReportTokens: 12_288,
|
|
18
|
+
maxDurationMs: 1_200_000,
|
|
19
|
+
},
|
|
20
|
+
deep: {
|
|
21
|
+
budgetUSD: 10,
|
|
22
|
+
maxSources: 100,
|
|
23
|
+
maxTokens: 4_000_000,
|
|
24
|
+
maxTurns: 100,
|
|
25
|
+
maxReportTokens: 16_384,
|
|
26
|
+
maxDurationMs: 2_400_000,
|
|
27
|
+
},
|
|
28
|
+
max: {
|
|
29
|
+
budgetUSD: 40,
|
|
30
|
+
maxSources: 250,
|
|
31
|
+
maxTokens: 16_000_000,
|
|
32
|
+
maxTurns: 150,
|
|
33
|
+
maxReportTokens: 24_576,
|
|
34
|
+
maxDurationMs: 3_600_000,
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
for (const envelope of Object.values(EFFORT_ENVELOPES)) {
|
|
38
|
+
Object.freeze(envelope);
|
|
39
|
+
}
|
|
40
|
+
Object.freeze(EFFORT_ENVELOPES);
|
|
41
|
+
const DEFAULT_MODEL_CONCURRENCY = 4;
|
|
42
|
+
const DEFAULT_IO_CONCURRENCY = 10;
|
|
43
|
+
function resolveConcurrency(configured, fallback, ...keys) {
|
|
44
|
+
if (configured !== undefined &&
|
|
45
|
+
Number.isFinite(configured) &&
|
|
46
|
+
configured >= 1) {
|
|
47
|
+
return Math.floor(configured);
|
|
48
|
+
}
|
|
49
|
+
const raw = readEnv(...keys);
|
|
50
|
+
if (raw === undefined)
|
|
51
|
+
return fallback;
|
|
52
|
+
const parsed = Number(raw);
|
|
53
|
+
if (!Number.isFinite(parsed) || parsed < 1)
|
|
54
|
+
return fallback;
|
|
55
|
+
return Math.floor(parsed);
|
|
56
|
+
}
|
|
57
|
+
export function resolveRunConfig(config, options) {
|
|
58
|
+
if (!config.model || typeof config.model === "string") {
|
|
59
|
+
throw new AtlasError('Atlas requires a model instance (e.g. anthropic("claude-fable-5")); model id strings are not accepted', "config");
|
|
60
|
+
}
|
|
61
|
+
const effort = options.effort ?? config.effort ?? "balanced";
|
|
62
|
+
const envelope = EFFORT_ENVELOPES[effort];
|
|
63
|
+
if (!envelope) {
|
|
64
|
+
throw new AtlasError(`unknown effort "${effort}" (expected fast | balanced | deep | max)`, "config");
|
|
65
|
+
}
|
|
66
|
+
const budget = { ...config.budget, ...options.budget };
|
|
67
|
+
const budgetUSD = budget.maxUSD ?? envelope.budgetUSD;
|
|
68
|
+
if (!Number.isFinite(budgetUSD) || budgetUSD <= 0) {
|
|
69
|
+
throw new AtlasError(`budget.maxUSD must be > 0 (got ${budgetUSD})`, "config");
|
|
70
|
+
}
|
|
71
|
+
const maxTokens = budget.maxTokens ?? envelope.maxTokens;
|
|
72
|
+
if (!Number.isFinite(maxTokens) || maxTokens <= 0) {
|
|
73
|
+
throw new AtlasError(`budget.maxTokens must be > 0 (got ${maxTokens})`, "config");
|
|
74
|
+
}
|
|
75
|
+
const maxSources = budget.maxSources ?? envelope.maxSources;
|
|
76
|
+
if (!Number.isFinite(maxSources) || maxSources <= 0) {
|
|
77
|
+
throw new AtlasError(`budget.maxSources must be > 0 (got ${maxSources})`, "config");
|
|
78
|
+
}
|
|
79
|
+
const maxDurationMs = budget.maxDurationMs ?? envelope.maxDurationMs;
|
|
80
|
+
if (maxDurationMs !== undefined &&
|
|
81
|
+
(!Number.isFinite(maxDurationMs) || maxDurationMs <= 0)) {
|
|
82
|
+
throw new AtlasError(`budget.maxDurationMs must be > 0 (got ${maxDurationMs})`, "config");
|
|
83
|
+
}
|
|
84
|
+
const lead = config.model;
|
|
85
|
+
const models = {
|
|
86
|
+
lead,
|
|
87
|
+
research: config.models?.research ?? lead,
|
|
88
|
+
write: config.models?.write ?? lead,
|
|
89
|
+
};
|
|
90
|
+
const leadModelId = lead.modelId ?? "";
|
|
91
|
+
return {
|
|
92
|
+
effort,
|
|
93
|
+
envelope,
|
|
94
|
+
budgetUSD,
|
|
95
|
+
maxTokens,
|
|
96
|
+
maxDurationMs,
|
|
97
|
+
maxSources,
|
|
98
|
+
models,
|
|
99
|
+
leadModelId,
|
|
100
|
+
pricing: config.pricing ?? {},
|
|
101
|
+
safety: config.safety ?? {},
|
|
102
|
+
sourceFilter: options.sources,
|
|
103
|
+
instructions: config.instructions,
|
|
104
|
+
maxConcurrentModelCalls: resolveConcurrency(config.concurrency?.models, DEFAULT_MODEL_CONCURRENCY, "ATLAS_MODEL_CONCURRENCY"),
|
|
105
|
+
maxConcurrentIo: resolveConcurrency(config.concurrency?.io, DEFAULT_IO_CONCURRENCY, "ATLAS_IO_CONCURRENCY"),
|
|
106
|
+
trace: options.trace ?? config.trace ?? "off",
|
|
107
|
+
researchers: config.researchers ?? {},
|
|
108
|
+
};
|
|
109
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { type BudgetGrant, type BudgetMeter } from "./budget.js";
|
|
2
|
+
import type { AtlasConfig, ResolvedRunConfig } from "./config.js";
|
|
3
|
+
import type { EventHub } from "./event-hub.js";
|
|
4
|
+
import type { JournalWriter, ReplayCache } from "./providers/store.js";
|
|
5
|
+
import { type RunCtx } from "./state.js";
|
|
6
|
+
export interface AssembleRunArgs {
|
|
7
|
+
runId: string;
|
|
8
|
+
question: string;
|
|
9
|
+
todayISO: string;
|
|
10
|
+
resolved: ResolvedRunConfig;
|
|
11
|
+
config: AtlasConfig;
|
|
12
|
+
journal: JournalWriter;
|
|
13
|
+
replay?: ReplayCache | undefined;
|
|
14
|
+
hub: EventHub;
|
|
15
|
+
hardSignal: AbortSignal;
|
|
16
|
+
stopSignal: AbortSignal;
|
|
17
|
+
now: () => number;
|
|
18
|
+
startedAt: number;
|
|
19
|
+
}
|
|
20
|
+
export interface RunAssembly {
|
|
21
|
+
rctx: RunCtx;
|
|
22
|
+
meter: BudgetMeter;
|
|
23
|
+
synthesisGrant: BudgetGrant;
|
|
24
|
+
}
|
|
25
|
+
export declare function assembleRun(args: AssembleRunArgs): Promise<RunAssembly>;
|
|
26
|
+
export declare function deriveChildCtx(parent: RunCtx, question: string): RunCtx;
|
package/dist/context.js
ADDED
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
import { createAdaptiveLimit, createConcurrencyGate, createDynamicConcurrencyGate, } from "./async.js";
|
|
2
|
+
import { createBudgetMeter, DEFAULT_PRICING, } from "./budget.js";
|
|
3
|
+
import { resolveCustomTools } from "./custom-tools.js";
|
|
4
|
+
import { isSmallModelId } from "./defaults.js";
|
|
5
|
+
import { ECONOMY } from "./economy.js";
|
|
6
|
+
import { createModelCallCache, createRunUsage, engineModel, totalFreshTokens, } from "./model.js";
|
|
7
|
+
import { defaultFetchProviders } from "./providers/fetch.js";
|
|
8
|
+
import { combineSearchProviders, defaultSearchProviders, } from "./providers/search.js";
|
|
9
|
+
import { createSafeDispatcher } from "./safe-dispatcher.js";
|
|
10
|
+
import { isRunCodeAvailable } from "./sandbox.js";
|
|
11
|
+
import { createRunCounters, createSourceStore, } from "./state.js";
|
|
12
|
+
import { createTraceRecorder } from "./trace.js";
|
|
13
|
+
import { createTrail } from "./trail.js";
|
|
14
|
+
const TIMEOUT_SYNTHESIS_RESERVE_MS = 120_000;
|
|
15
|
+
const BUDGET_WARNING_FRACTIONS = [0.5, 0.8, 0.95];
|
|
16
|
+
const UNJOURNALED_EVENTS = new Set([
|
|
17
|
+
"report.delta",
|
|
18
|
+
"report.reset",
|
|
19
|
+
]);
|
|
20
|
+
export async function assembleRun(args) {
|
|
21
|
+
const { runId, question, resolved } = args;
|
|
22
|
+
const meter = createBudgetMeter(resolved.budgetUSD);
|
|
23
|
+
const usage = createRunUsage();
|
|
24
|
+
const pricing = { ...DEFAULT_PRICING, ...resolved.pricing };
|
|
25
|
+
const makeModelLimit = () => createAdaptiveLimit({
|
|
26
|
+
start: resolved.maxConcurrentModelCalls,
|
|
27
|
+
min: Math.min(2, resolved.maxConcurrentModelCalls),
|
|
28
|
+
max: resolved.maxConcurrentModelCalls * 2,
|
|
29
|
+
});
|
|
30
|
+
const heavyLimit = makeModelLimit();
|
|
31
|
+
const smallLimit = makeModelLimit();
|
|
32
|
+
const ioGate = createConcurrencyGate(resolved.maxConcurrentIo);
|
|
33
|
+
const modelCache = createModelCallCache();
|
|
34
|
+
const callOrdinals = new Map();
|
|
35
|
+
const counters = createRunCounters();
|
|
36
|
+
const warnedUnknownModels = new Set();
|
|
37
|
+
const warnedFractions = new Set();
|
|
38
|
+
const deadlineAt = resolved.maxDurationMs
|
|
39
|
+
? args.startedAt + resolved.maxDurationMs
|
|
40
|
+
: undefined;
|
|
41
|
+
const recorder = resolved.trace !== "off"
|
|
42
|
+
? createTraceRecorder({
|
|
43
|
+
mode: resolved.trace,
|
|
44
|
+
now: args.now,
|
|
45
|
+
startedAt: args.startedAt,
|
|
46
|
+
})
|
|
47
|
+
: undefined;
|
|
48
|
+
recorder?.setSink((kind, _id, data) => args.journal.trace(kind, data));
|
|
49
|
+
const budgetExhausted = () => meter.exhausted();
|
|
50
|
+
const tokensExhausted = () => totalFreshTokens(usage) >= resolved.maxTokens;
|
|
51
|
+
const emit = (event) => {
|
|
52
|
+
args.hub.emit(event);
|
|
53
|
+
if (!UNJOURNALED_EVENTS.has(event.type)) {
|
|
54
|
+
args.journal.event(event.type, event);
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
const trackGateWidth = () => {
|
|
58
|
+
counters.modelGatePeakWidth = Math.max(counters.modelGatePeakWidth, heavyLimit.value() + smallLimit.value());
|
|
59
|
+
};
|
|
60
|
+
const emitBudgetWarnings = () => {
|
|
61
|
+
const fraction = meter.totalSpentUSD() / meter.totalUSD;
|
|
62
|
+
for (const threshold of BUDGET_WARNING_FRACTIONS) {
|
|
63
|
+
if (fraction >= threshold && !warnedFractions.has(threshold)) {
|
|
64
|
+
warnedFractions.add(threshold);
|
|
65
|
+
emit({
|
|
66
|
+
type: "budget.warning",
|
|
67
|
+
spentUSD: meter.totalSpentUSD(),
|
|
68
|
+
limitUSD: meter.totalUSD,
|
|
69
|
+
fraction: threshold,
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
const makeTier = (limit) => ({
|
|
75
|
+
gate: createDynamicConcurrencyGate(() => limit.value()),
|
|
76
|
+
onCost: () => {
|
|
77
|
+
limit.onSuccess();
|
|
78
|
+
trackGateWidth();
|
|
79
|
+
emitBudgetWarnings();
|
|
80
|
+
},
|
|
81
|
+
onRateLimit: ({ delayMs }) => {
|
|
82
|
+
limit.onThrottle();
|
|
83
|
+
emit({
|
|
84
|
+
type: "rate.limited",
|
|
85
|
+
retryAfterSeconds: Math.max(1, Math.round(delayMs / 1000)),
|
|
86
|
+
});
|
|
87
|
+
},
|
|
88
|
+
});
|
|
89
|
+
const heavyTier = makeTier(heavyLimit);
|
|
90
|
+
const smallTier = makeTier(smallLimit);
|
|
91
|
+
const tierForRole = (role) => isSmallModelId(resolved.models[role].modelId ?? "")
|
|
92
|
+
? smallTier
|
|
93
|
+
: heavyTier;
|
|
94
|
+
const onUnknownModel = (modelId) => {
|
|
95
|
+
if (warnedUnknownModels.has(modelId))
|
|
96
|
+
return;
|
|
97
|
+
warnedUnknownModels.add(modelId);
|
|
98
|
+
emit({
|
|
99
|
+
type: "pricing.missing",
|
|
100
|
+
modelId,
|
|
101
|
+
detail: `no pricing entry for model "${modelId}"; charging neutral mid-tier default rates — set config.pricing for accurate cost`,
|
|
102
|
+
});
|
|
103
|
+
};
|
|
104
|
+
const bindModelWithTier = (role, grant, tier) => engineModel(resolved.models[role], {
|
|
105
|
+
role,
|
|
106
|
+
grant,
|
|
107
|
+
pricing,
|
|
108
|
+
gate: tier.gate,
|
|
109
|
+
usage,
|
|
110
|
+
journal: args.journal,
|
|
111
|
+
replay: args.replay,
|
|
112
|
+
modelCache,
|
|
113
|
+
callOrdinals,
|
|
114
|
+
recorder,
|
|
115
|
+
onCost: tier.onCost,
|
|
116
|
+
onUnknownModel,
|
|
117
|
+
onRateLimit: tier.onRateLimit,
|
|
118
|
+
onCacheHit: () => {
|
|
119
|
+
counters.modelCacheHits++;
|
|
120
|
+
},
|
|
121
|
+
budgetExhausted: () => grant.spentUSD() >= grant.limitUSD,
|
|
122
|
+
});
|
|
123
|
+
const bindModel = (role, grant) => bindModelWithTier(role, grant, tierForRole(role));
|
|
124
|
+
const { search, searchBySource } = resolveSearchConfig(args.config.search, () => defaultSearchProviders(bindModelWithTier("research", meter, tierForRole("research"))));
|
|
125
|
+
const fetchChain = Array.isArray(args.config.fetch)
|
|
126
|
+
? args.config.fetch
|
|
127
|
+
: args.config.fetch
|
|
128
|
+
? [args.config.fetch]
|
|
129
|
+
: defaultFetchProviders();
|
|
130
|
+
const safeDispatcher = createSafeDispatcher(resolved.safety);
|
|
131
|
+
const customTools = await resolveCustomTools(args.config.tools);
|
|
132
|
+
const runCodeEnabled = await isRunCodeAvailable();
|
|
133
|
+
const synthesisGrant = meter.grant({
|
|
134
|
+
fraction: ECONOMY.synthesis.fraction,
|
|
135
|
+
minUSD: ECONOMY.synthesis.minUSD,
|
|
136
|
+
}) ?? meter;
|
|
137
|
+
const rctx = {
|
|
138
|
+
runId,
|
|
139
|
+
question,
|
|
140
|
+
todayISO: args.todayISO,
|
|
141
|
+
config: resolved,
|
|
142
|
+
meter,
|
|
143
|
+
usage,
|
|
144
|
+
pricing,
|
|
145
|
+
ledger: null,
|
|
146
|
+
trail: createTrail(),
|
|
147
|
+
notes: [],
|
|
148
|
+
readCounts: new Map(),
|
|
149
|
+
sources: createSourceStore(),
|
|
150
|
+
search,
|
|
151
|
+
searchBySource,
|
|
152
|
+
oaCandidates: new Map(),
|
|
153
|
+
surfacedCandidates: new Map(),
|
|
154
|
+
fetchChain,
|
|
155
|
+
safeDispatcher,
|
|
156
|
+
customTools,
|
|
157
|
+
runCodeEnabled,
|
|
158
|
+
emit,
|
|
159
|
+
journal: args.journal,
|
|
160
|
+
replay: args.replay,
|
|
161
|
+
recorder,
|
|
162
|
+
signal: args.hardSignal,
|
|
163
|
+
stopSignal: args.stopSignal,
|
|
164
|
+
deadlineAt,
|
|
165
|
+
now: args.now,
|
|
166
|
+
modelGate: heavyTier.gate,
|
|
167
|
+
ioGate,
|
|
168
|
+
seenDomains: new Set(),
|
|
169
|
+
counters,
|
|
170
|
+
agentSequence: { next: 1 },
|
|
171
|
+
bindModel,
|
|
172
|
+
rawModel: (role) => resolved.models[role],
|
|
173
|
+
stopReason: () => {
|
|
174
|
+
if (args.stopSignal.aborted)
|
|
175
|
+
return "stop requested";
|
|
176
|
+
if (deadlineAt !== undefined &&
|
|
177
|
+
args.now() > deadlineAt - TIMEOUT_SYNTHESIS_RESERVE_MS) {
|
|
178
|
+
return "timeout approaching";
|
|
179
|
+
}
|
|
180
|
+
if (budgetExhausted())
|
|
181
|
+
return "budget exhausted";
|
|
182
|
+
if (tokensExhausted())
|
|
183
|
+
return "token budget reached";
|
|
184
|
+
return null;
|
|
185
|
+
},
|
|
186
|
+
};
|
|
187
|
+
if (args.replay)
|
|
188
|
+
primeSourceNumbers(rctx.sources, args.replay);
|
|
189
|
+
return {
|
|
190
|
+
rctx,
|
|
191
|
+
meter,
|
|
192
|
+
synthesisGrant,
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
function isSearchProvider(value) {
|
|
196
|
+
return (typeof value === "object" &&
|
|
197
|
+
value !== null &&
|
|
198
|
+
typeof value.id === "string" &&
|
|
199
|
+
typeof value.search === "function");
|
|
200
|
+
}
|
|
201
|
+
function resolveSearchConfig(raw, webFallback) {
|
|
202
|
+
const flat = (providers) => {
|
|
203
|
+
const web = combineSearchProviders(providers.length > 0 ? providers : webFallback());
|
|
204
|
+
return { search: web, searchBySource: new Map([["web", web]]) };
|
|
205
|
+
};
|
|
206
|
+
if (raw === undefined)
|
|
207
|
+
return flat([]);
|
|
208
|
+
if (isSearchProvider(raw))
|
|
209
|
+
return flat([raw]);
|
|
210
|
+
if (Array.isArray(raw))
|
|
211
|
+
return flat(raw);
|
|
212
|
+
const searchBySource = new Map();
|
|
213
|
+
for (const [source, value] of Object.entries(raw)) {
|
|
214
|
+
const providers = Array.isArray(value) ? value : [value];
|
|
215
|
+
if (providers.length > 0) {
|
|
216
|
+
searchBySource.set(source, combineSearchProviders(providers));
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
if (!searchBySource.has("web")) {
|
|
220
|
+
searchBySource.set("web", combineSearchProviders(webFallback()));
|
|
221
|
+
}
|
|
222
|
+
return { search: searchBySource.get("web"), searchBySource };
|
|
223
|
+
}
|
|
224
|
+
function primeSourceNumbers(sources, replay) {
|
|
225
|
+
let max = 0;
|
|
226
|
+
for (const prefix of ["fetch:", "custom-source:"]) {
|
|
227
|
+
for (const value of replay.values(prefix)) {
|
|
228
|
+
const sourceId = value?.sourceId;
|
|
229
|
+
const match = typeof sourceId === "string" ? /^source_(\d+)$/.exec(sourceId) : null;
|
|
230
|
+
if (match)
|
|
231
|
+
max = Math.max(max, Number(match[1]));
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
if (max >= sources.nextSourceNumber)
|
|
235
|
+
sources.nextSourceNumber = max + 1;
|
|
236
|
+
}
|
|
237
|
+
export function deriveChildCtx(parent, question) {
|
|
238
|
+
return {
|
|
239
|
+
...parent,
|
|
240
|
+
question,
|
|
241
|
+
ledger: null,
|
|
242
|
+
trail: createTrail(),
|
|
243
|
+
notes: [],
|
|
244
|
+
readCounts: new Map(),
|
|
245
|
+
sources: createSourceStore(),
|
|
246
|
+
oaCandidates: new Map(),
|
|
247
|
+
surfacedCandidates: new Map(),
|
|
248
|
+
seenDomains: new Set(),
|
|
249
|
+
};
|
|
250
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { type FlexibleSchema } from "ai";
|
|
2
|
+
export interface ToolContext {
|
|
3
|
+
addSource(source: {
|
|
4
|
+
url: string;
|
|
5
|
+
title?: string;
|
|
6
|
+
content: string;
|
|
7
|
+
}): void;
|
|
8
|
+
fetchText(url: string): Promise<string | null>;
|
|
9
|
+
readonly signal?: AbortSignal | undefined;
|
|
10
|
+
log(message: string): void;
|
|
11
|
+
}
|
|
12
|
+
export interface ResearchTool<I = unknown> {
|
|
13
|
+
description: string;
|
|
14
|
+
inputSchema: FlexibleSchema<I>;
|
|
15
|
+
timeoutMs?: number;
|
|
16
|
+
execute(input: I, ctx: ToolContext): string | Promise<string>;
|
|
17
|
+
}
|
|
18
|
+
export interface ResolvedCustomTool {
|
|
19
|
+
name: string;
|
|
20
|
+
description: string;
|
|
21
|
+
inputJsonSchema: Record<string, unknown>;
|
|
22
|
+
timeoutMs?: number | undefined;
|
|
23
|
+
execute(input: unknown, ctx: ToolContext): string | Promise<string>;
|
|
24
|
+
}
|
|
25
|
+
export declare function researchTool<I>(tool: ResearchTool<I>): ResearchTool<I>;
|
|
26
|
+
export declare function resolveCustomTools(tools: Record<string, ResearchTool> | undefined): Promise<Map<string, ResolvedCustomTool>>;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { asSchema } from "ai";
|
|
2
|
+
import { AtlasError } from "./errors.js";
|
|
3
|
+
import { BUILTIN_TOOL_NAMES, LEDGER_TOOL_NAMES } from "./tools.js";
|
|
4
|
+
const RESERVED_TOOL_NAMES = new Set([
|
|
5
|
+
...BUILTIN_TOOL_NAMES,
|
|
6
|
+
...LEDGER_TOOL_NAMES,
|
|
7
|
+
]);
|
|
8
|
+
const TOOL_NAME_PATTERN = /^[A-Za-z][\w-]{0,63}$/;
|
|
9
|
+
export function researchTool(tool) {
|
|
10
|
+
return tool;
|
|
11
|
+
}
|
|
12
|
+
export async function resolveCustomTools(tools) {
|
|
13
|
+
const resolved = new Map();
|
|
14
|
+
if (!tools)
|
|
15
|
+
return resolved;
|
|
16
|
+
for (const [name, tool] of Object.entries(tools)) {
|
|
17
|
+
if (RESERVED_TOOL_NAMES.has(name)) {
|
|
18
|
+
throw new AtlasError(`custom tool "${name}" would shadow the builtin ${name} tool; pick another name`, "config");
|
|
19
|
+
}
|
|
20
|
+
if (!TOOL_NAME_PATTERN.test(name)) {
|
|
21
|
+
throw new AtlasError(`custom tool name "${name}" is invalid: use 1-64 letters, digits, _ or -, starting with a letter`, "config");
|
|
22
|
+
}
|
|
23
|
+
const jsonSchemaObject = await Promise.resolve(asSchema(tool.inputSchema).jsonSchema);
|
|
24
|
+
resolved.set(name, {
|
|
25
|
+
name,
|
|
26
|
+
description: tool.description,
|
|
27
|
+
inputJsonSchema: jsonSchemaObject,
|
|
28
|
+
timeoutMs: tool.timeoutMs,
|
|
29
|
+
execute: tool.execute,
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
return resolved;
|
|
33
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { ResolvedModel } from "./model.js";
|
|
2
|
+
export declare const DEFAULT_ANTHROPIC_MODEL = "claude-opus-4-8";
|
|
3
|
+
export declare const DEFAULT_OPENAI_MODEL = "gpt-5.5";
|
|
4
|
+
export declare const DEFAULT_ZAI_MODEL = "glm-5.2";
|
|
5
|
+
export declare const DEFAULT_ZAI_BASE_URL = "https://api.z.ai/api/paas/v4";
|
|
6
|
+
export declare function isSmallModelId(modelId: string): boolean;
|
|
7
|
+
export declare function isZaiModelId(modelId: string): boolean;
|
|
8
|
+
export declare function contextWindowTokens(model: ResolvedModel): number;
|
|
9
|
+
export declare function extractionCharsFor(model: ResolvedModel): number;
|
|
10
|
+
export declare function leadContextTokensFor(model: ResolvedModel): number;
|
package/dist/defaults.js
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
export const DEFAULT_ANTHROPIC_MODEL = "claude-opus-4-8";
|
|
2
|
+
export const DEFAULT_OPENAI_MODEL = "gpt-5.5";
|
|
3
|
+
export const DEFAULT_ZAI_MODEL = "glm-5.2";
|
|
4
|
+
export const DEFAULT_ZAI_BASE_URL = "https://api.z.ai/api/paas/v4";
|
|
5
|
+
const SMALL_MODEL_ID_PATTERN = /haiku|mini|nano|flash|lite/i;
|
|
6
|
+
const ZAI_MODEL_ID_PATTERN = /^glm-/i;
|
|
7
|
+
const ZAI_SMALL_MODEL_ID_PATTERN = /^glm-4\.5-air$/i;
|
|
8
|
+
export function isSmallModelId(modelId) {
|
|
9
|
+
return (SMALL_MODEL_ID_PATTERN.test(modelId) ||
|
|
10
|
+
ZAI_SMALL_MODEL_ID_PATTERN.test(modelId));
|
|
11
|
+
}
|
|
12
|
+
export function isZaiModelId(modelId) {
|
|
13
|
+
return ZAI_MODEL_ID_PATTERN.test(modelId);
|
|
14
|
+
}
|
|
15
|
+
const CONTEXT_WINDOW_BY_PATTERN = [
|
|
16
|
+
[/haiku|mini|nano|flash|lite/i, 200_000],
|
|
17
|
+
[/opus|sonnet|fable|mythos/i, 1_000_000],
|
|
18
|
+
];
|
|
19
|
+
const DEFAULT_CONTEXT_WINDOW_TOKENS = 200_000;
|
|
20
|
+
const CHARS_PER_TOKEN = 4;
|
|
21
|
+
const EXTRACTION_CONTEXT_FRACTION = 0.3;
|
|
22
|
+
const LEAD_CONTEXT_TARGET_TOKENS = 80_000;
|
|
23
|
+
const LEAD_CONTEXT_MAX_FRACTION = 0.4;
|
|
24
|
+
export function contextWindowTokens(model) {
|
|
25
|
+
const modelId = model.modelId ?? "";
|
|
26
|
+
for (const [pattern, window] of CONTEXT_WINDOW_BY_PATTERN) {
|
|
27
|
+
if (pattern.test(modelId))
|
|
28
|
+
return window;
|
|
29
|
+
}
|
|
30
|
+
return DEFAULT_CONTEXT_WINDOW_TOKENS;
|
|
31
|
+
}
|
|
32
|
+
export function extractionCharsFor(model) {
|
|
33
|
+
return Math.round(contextWindowTokens(model) * EXTRACTION_CONTEXT_FRACTION * CHARS_PER_TOKEN);
|
|
34
|
+
}
|
|
35
|
+
export function leadContextTokensFor(model) {
|
|
36
|
+
return Math.min(LEAD_CONTEXT_TARGET_TOKENS, Math.round(contextWindowTokens(model) * LEAD_CONTEXT_MAX_FRACTION));
|
|
37
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export declare const ECONOMY: {
|
|
2
|
+
readonly grantFloorUSD: 0.02;
|
|
3
|
+
readonly defaultGrantFraction: 0.15;
|
|
4
|
+
readonly synthesis: {
|
|
5
|
+
readonly fraction: 0.15;
|
|
6
|
+
readonly minUSD: 0.05;
|
|
7
|
+
};
|
|
8
|
+
readonly callReserve: {
|
|
9
|
+
readonly promptCharsPerToken: 4;
|
|
10
|
+
readonly assumedOutputTokens: 2000;
|
|
11
|
+
};
|
|
12
|
+
};
|
package/dist/economy.js
ADDED
package/dist/env.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function readEnv(...keys: string[]): string | undefined;
|
package/dist/env.js
ADDED
package/dist/errors.d.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export declare function errorMessage(err: unknown): string;
|
|
2
|
+
export type AtlasErrorCode = "config" | "budget" | "resume" | "aborted" | "paused" | "timeout";
|
|
3
|
+
export declare class AtlasError extends Error {
|
|
4
|
+
readonly code: AtlasErrorCode;
|
|
5
|
+
constructor(message: string, code: AtlasErrorCode);
|
|
6
|
+
}
|
package/dist/errors.js
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { ResearchEvent } from "./events.js";
|
|
2
|
+
export declare class EventHub {
|
|
3
|
+
private readonly subscribers;
|
|
4
|
+
private readonly history;
|
|
5
|
+
private closed;
|
|
6
|
+
private failure;
|
|
7
|
+
emit(event: ResearchEvent): void;
|
|
8
|
+
close(): void;
|
|
9
|
+
fail(error: unknown): void;
|
|
10
|
+
iterable(): AsyncIterable<ResearchEvent>;
|
|
11
|
+
}
|