@wardby/cli 0.3.0 → 0.4.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/.env.example +22 -3
- package/README.md +11 -9
- package/dist/cli-help.d.ts +1 -1
- package/dist/cli-help.js +1 -0
- package/dist/cli.js +68 -10
- package/dist/coding/base-commit.d.ts +6 -0
- package/dist/coding/base-commit.js +12 -0
- package/dist/coding/protocol.d.ts +17 -1
- package/dist/coding/protocol.js +17 -6
- package/dist/coding/provider.d.ts +6 -1
- package/dist/coding/provider.js +16 -9
- package/dist/config/providers.d.ts +25 -0
- package/dist/config/providers.js +71 -0
- package/dist/core/attribution.d.ts +101 -0
- package/dist/core/attribution.js +208 -0
- package/dist/core/budget-groups.d.ts +17 -10
- package/dist/core/budget-groups.js +15 -12
- package/dist/core/coding-queue.d.ts +3 -0
- package/dist/core/coding-queue.js +6 -2
- package/dist/core/coding-service-status.d.ts +11 -0
- package/dist/core/coding-service-status.js +17 -0
- package/dist/core/cost-report.d.ts +88 -0
- package/dist/core/cost-report.js +248 -0
- package/dist/core/dispatch.d.ts +42 -2
- package/dist/core/dispatch.js +144 -26
- package/dist/core/engine-native.js +17 -4
- package/dist/core/glob.d.ts +10 -0
- package/dist/core/glob.js +33 -0
- package/dist/core/host-events.d.ts +24 -1
- package/dist/core/host-events.js +341 -1
- package/dist/core/host-status.d.ts +19 -2
- package/dist/core/host-status.js +47 -30
- package/dist/core/issue-bridge.d.ts +60 -0
- package/dist/core/issue-bridge.js +189 -0
- package/dist/core/issue-dedupe.d.ts +70 -0
- package/dist/core/issue-dedupe.js +255 -0
- package/dist/core/issue-events.d.ts +42 -0
- package/dist/core/issue-events.js +155 -0
- package/dist/core/issue-status.d.ts +29 -0
- package/dist/core/issue-status.js +241 -0
- package/dist/core/issue-tracker-tools.d.ts +64 -0
- package/dist/core/issue-tracker-tools.js +850 -0
- package/dist/core/model-usage.d.ts +10 -0
- package/dist/core/model-usage.js +24 -0
- package/dist/core/reconciler.d.ts +8 -4
- package/dist/core/reconciler.js +15 -4
- package/dist/core/review-host-tools.js +10 -3
- package/dist/core/run-pricing.d.ts +61 -0
- package/dist/core/run-pricing.js +56 -0
- package/dist/core/runner.d.ts +5 -2
- package/dist/core/runner.js +178 -27
- package/dist/core/scheduler.d.ts +4 -1
- package/dist/core/scheduler.js +3 -2
- package/dist/core/self-defects.d.ts +80 -0
- package/dist/core/self-defects.js +180 -0
- package/dist/core/tool-names.js +3 -0
- package/dist/core/webhooks.d.ts +9 -1
- package/dist/core/webhooks.js +19 -1
- package/dist/env.js +6 -1
- package/dist/generated/prisma/browser.d.ts +66 -0
- package/dist/generated/prisma/client.d.ts +66 -0
- package/dist/generated/prisma/commonInputTypes.d.ts +122 -52
- package/dist/generated/prisma/enums.d.ts +7 -0
- package/dist/generated/prisma/enums.js +6 -0
- package/dist/generated/prisma/internal/class.d.ts +99 -0
- package/dist/generated/prisma/internal/class.js +4 -4
- package/dist/generated/prisma/internal/prismaNamespace.d.ts +826 -1
- package/dist/generated/prisma/internal/prismaNamespace.js +135 -2
- package/dist/generated/prisma/internal/prismaNamespaceBrowser.d.ts +142 -0
- package/dist/generated/prisma/internal/prismaNamespaceBrowser.js +135 -2
- package/dist/generated/prisma/models/Agent.d.ts +389 -1
- package/dist/generated/prisma/models/AgentIssueProject.d.ts +1838 -0
- package/dist/generated/prisma/models/AgentIssueProject.js +1 -0
- package/dist/generated/prisma/models/AgentRepository.d.ts +1 -1
- package/dist/generated/prisma/models/AuthUser.d.ts +1 -1
- package/dist/generated/prisma/models/CodingProxySession.d.ts +73 -1
- package/dist/generated/prisma/models/CodingRun.d.ts +130 -1
- package/dist/generated/prisma/models/CodingRunServiceStatus.d.ts +1404 -0
- package/dist/generated/prisma/models/CodingRunServiceStatus.js +1 -0
- package/dist/generated/prisma/models/IssueFingerprint.d.ts +1183 -0
- package/dist/generated/prisma/models/IssueFingerprint.js +1 -0
- package/dist/generated/prisma/models/IssuePullRequest.d.ts +1255 -0
- package/dist/generated/prisma/models/IssuePullRequest.js +1 -0
- package/dist/generated/prisma/models/ModelCatalogEntry.d.ts +1322 -0
- package/dist/generated/prisma/models/ModelCatalogEntry.js +1 -0
- package/dist/generated/prisma/models/Run.d.ts +933 -1
- package/dist/generated/prisma/models/RunAttribution.d.ts +1259 -0
- package/dist/generated/prisma/models/RunAttribution.js +1 -0
- package/dist/generated/prisma/models/RunIssueStatus.d.ts +1199 -0
- package/dist/generated/prisma/models/RunIssueStatus.js +1 -0
- package/dist/generated/prisma/models/RunModelUsage.d.ts +1316 -0
- package/dist/generated/prisma/models/RunModelUsage.js +1 -0
- package/dist/generated/prisma/models/WorkItem.d.ts +1408 -0
- package/dist/generated/prisma/models/WorkItem.js +1 -0
- package/dist/generated/prisma/models.d.ts +9 -0
- package/dist/help-index.json +355 -16
- package/dist/import/neutral-schema.d.ts +16 -16
- package/dist/knowledge/check.d.ts +13 -0
- package/dist/knowledge/check.js +69 -0
- package/dist/knowledge/cli.d.ts +14 -0
- package/dist/knowledge/cli.js +67 -0
- package/dist/knowledge/concept.d.ts +54 -0
- package/dist/knowledge/concept.js +78 -0
- package/dist/knowledge/note.d.ts +11 -0
- package/dist/knowledge/note.js +39 -0
- package/dist/knowledge/relevance.d.ts +11 -0
- package/dist/knowledge/relevance.js +14 -0
- package/dist/knowledge/span-hash.d.ts +3 -0
- package/dist/knowledge/span-hash.js +16 -0
- package/dist/mcp/auth/access.d.ts +4 -2
- package/dist/mcp/auth/ownership.d.ts +9 -9
- package/dist/mcp/auth/resource-server.d.ts +3 -1
- package/dist/mcp/auth/resource-server.js +18 -3
- package/dist/mcp/auth/self-hosted/credentials.d.ts +3 -3
- package/dist/mcp/auth/self-hosted/session.d.ts +5 -5
- package/dist/mcp/context.d.ts +3 -0
- package/dist/mcp/host-events/deliveries.d.ts +9 -0
- package/dist/mcp/host-events/deliveries.js +17 -0
- package/dist/mcp/host-events/github-ingress.d.ts +4 -2
- package/dist/mcp/host-events/github-ingress.js +4 -13
- package/dist/mcp/host-events/jira-ingress.d.ts +29 -0
- package/dist/mcp/host-events/jira-ingress.js +92 -0
- package/dist/mcp/index.d.ts +2 -0
- package/dist/mcp/index.js +87 -9
- package/dist/mcp/server.js +5 -2
- package/dist/mcp/tools/agents.js +74 -3
- package/dist/mcp/tools/cost-report.d.ts +8 -0
- package/dist/mcp/tools/cost-report.js +60 -0
- package/dist/mcp/tools/issue-projects.d.ts +2 -0
- package/dist/mcp/tools/issue-projects.js +238 -0
- package/dist/mcp/tools/model-catalog.d.ts +22 -0
- package/dist/mcp/tools/model-catalog.js +423 -0
- package/dist/mcp/tools/repositories.js +2 -1
- package/dist/mcp/tools/tools.d.ts +2 -2
- package/dist/mcp/tools/trigger.js +33 -5
- package/dist/mcp/transport/streamable-http.d.ts +5 -0
- package/dist/mcp/transport/streamable-http.js +23 -1
- package/dist/mcp/webhooks/ingress.d.ts +2 -1
- package/dist/mcp/webhooks/ingress.js +9 -2
- package/dist/providers/auth/self-hosted.d.ts +8 -1
- package/dist/providers/auth/self-hosted.js +39 -2
- package/dist/providers/coding-proxy/memory-ledger.d.ts +1 -1
- package/dist/providers/coding-proxy/memory-ledger.js +10 -1
- package/dist/providers/coding-proxy/metering.d.ts +2 -1
- package/dist/providers/coding-proxy/metering.js +13 -2
- package/dist/providers/coding-proxy/prisma-ledger.js +59 -6
- package/dist/providers/coding-proxy/proxy.d.ts +12 -2
- package/dist/providers/coding-proxy/proxy.js +92 -30
- package/dist/providers/coding-proxy/types.d.ts +18 -1
- package/dist/providers/coding-proxy/types.js +12 -1
- package/dist/providers/engine/types.d.ts +19 -0
- package/dist/providers/executor/composition.js +9 -1
- package/dist/providers/executor/container.d.ts +30 -2
- package/dist/providers/executor/container.js +98 -17
- package/dist/providers/executor/dbos.d.ts +2 -0
- package/dist/providers/executor/dbos.js +7 -5
- package/dist/providers/executor/routing.d.ts +6 -0
- package/dist/providers/executor/routing.js +5 -0
- package/dist/providers/executor/types.d.ts +12 -0
- package/dist/providers/issue-tracker/adf.d.ts +31 -0
- package/dist/providers/issue-tracker/adf.js +181 -0
- package/dist/providers/issue-tracker/index.d.ts +5 -0
- package/dist/providers/issue-tracker/index.js +12 -0
- package/dist/providers/issue-tracker/jira-client.d.ts +41 -0
- package/dist/providers/issue-tracker/jira-client.js +151 -0
- package/dist/providers/issue-tracker/jira-events.d.ts +3 -0
- package/dist/providers/issue-tracker/jira-events.js +98 -0
- package/dist/providers/issue-tracker/jira.d.ts +116 -0
- package/dist/providers/issue-tracker/jira.js +502 -0
- package/dist/providers/issue-tracker/types.d.ts +269 -0
- package/dist/providers/issue-tracker/types.js +16 -0
- package/dist/providers/jobs/docker.d.ts +5 -1
- package/dist/providers/jobs/docker.js +61 -33
- package/dist/providers/jobs/kubernetes.d.ts +3 -0
- package/dist/providers/jobs/kubernetes.js +44 -4
- package/dist/providers/jobs/service-state.d.ts +22 -0
- package/dist/providers/jobs/service-state.js +17 -0
- package/dist/providers/llm/anthropic.d.ts +3 -3
- package/dist/providers/llm/anthropic.js +3 -9
- package/dist/providers/llm/bedrock.d.ts +3 -3
- package/dist/providers/llm/bedrock.js +3 -9
- package/dist/providers/llm/catalog-lookup.d.ts +10 -0
- package/dist/providers/llm/catalog-lookup.js +15 -0
- package/dist/providers/llm/catalog-shipped.d.ts +18 -0
- package/dist/providers/llm/catalog-shipped.js +197 -0
- package/dist/providers/llm/catalog-store.d.ts +58 -0
- package/dist/providers/llm/catalog-store.js +138 -0
- package/dist/providers/llm/catalog-types.d.ts +66 -0
- package/dist/providers/llm/catalog-types.js +64 -0
- package/dist/providers/llm/catalog.d.ts +61 -0
- package/dist/providers/llm/catalog.js +147 -0
- package/dist/providers/llm/claude-provider.d.ts +10 -14
- package/dist/providers/llm/claude-provider.js +11 -6
- package/dist/providers/llm/index.d.ts +9 -6
- package/dist/providers/llm/index.js +8 -5
- package/dist/providers/llm/openai.d.ts +14 -5
- package/dist/providers/llm/openai.js +24 -14
- package/dist/providers/llm/pricing-core.d.ts +5 -3
- package/dist/providers/llm/registration.js +8 -12
- package/dist/providers/llm/routing.d.ts +18 -17
- package/dist/providers/llm/routing.js +40 -24
- package/dist/providers/review-host/github-events.js +47 -1
- package/dist/providers/review-host/github.js +7 -6
- package/dist/providers/review-host/types.d.ts +25 -0
- package/dist/providers/vcs/git.js +2 -22
- package/dist/providers/vcs/github.d.ts +20 -0
- package/dist/providers/vcs/github.js +28 -2
- package/dist/providers/vcs/types.d.ts +6 -0
- package/dist/quickstart/index.d.ts +8 -0
- package/dist/quickstart/index.js +34 -34
- package/dist/serve.js +8 -2
- package/dist/viewer/api-schema.d.ts +2757 -0
- package/dist/viewer/api-schema.js +165 -0
- package/dist/viewer/build-schemas.d.ts +2 -0
- package/dist/viewer/build-schemas.js +18 -0
- package/dist/viewer/event-bus.d.ts +38 -0
- package/dist/viewer/event-bus.js +232 -0
- package/dist/viewer/graph.d.ts +40 -0
- package/dist/viewer/graph.js +243 -0
- package/dist/viewer/http.d.ts +30 -0
- package/dist/viewer/http.js +133 -0
- package/dist/viewer/run-detail.d.ts +4 -0
- package/dist/viewer/run-detail.js +61 -0
- package/dist/wardby-bin.js +5 -0
- package/docs/README.md +10 -0
- package/docs/agent-recipes.md +383 -0
- package/docs/code-review-agents.md +29 -2
- package/docs/coding-agent-setup.md +3 -0
- package/docs/coding-worker-isolation.md +39 -5
- package/docs/getting-started-gke.md +28 -11
- package/docs/getting-started-identity-provider.md +49 -38
- package/docs/getting-started.md +14 -0
- package/docs/jira-agents.md +649 -0
- package/docs/knowledge.md +387 -0
- package/docs/models.md +221 -0
- package/docs/security-deployment.md +19 -9
- package/docs/viewer-api.md +142 -0
- package/help/admin-viewer.md +39 -0
- package/help/agent-recipes.md +173 -0
- package/help/architecture-agent.md +189 -0
- package/help/builder-agent.md +80 -0
- package/help/code-review-agents.md +6 -0
- package/help/cost-attribution.md +67 -0
- package/help/creating-agents.md +22 -0
- package/help/deploy-gke.md +6 -0
- package/help/errors/model-unavailable.md +63 -0
- package/help/getting-started.md +1 -0
- package/help/github.md +18 -0
- package/help/identity-and-access.md +8 -3
- package/help/jira.md +135 -0
- package/help/knowledge.md +47 -0
- package/help/models.md +90 -0
- package/help/operating-agents.md +7 -1
- package/help/troubleshooting/budgets.md +6 -0
- package/package.json +5 -2
- package/prisma/migrations/20260930000000_jira_issue_projects/migration.sql +34 -0
- package/prisma/migrations/20261001000000_jira_phase2_allowlists/migration.sql +3 -0
- package/prisma/migrations/20261001010000_jira_link_types_allowlist/migration.sql +2 -0
- package/prisma/migrations/20261002000000_jira_coding_bridge/migration.sql +28 -0
- package/prisma/migrations/20261002010000_jira_issue_creation/migration.sql +25 -0
- package/prisma/migrations/20261003000000_issue_cost_attribution/migration.sql +56 -0
- package/prisma/migrations/20261003010000_coding_run_service_status/migration.sql +23 -0
- package/prisma/migrations/20261003020000_viewer_notify/migration.sql +54 -0
- package/prisma/migrations/20261003030000_viewer_notify_fixes/migration.sql +47 -0
- package/prisma/migrations/20261003040000_viewer_indexes/migration.sql +12 -0
- package/prisma/migrations/20261004000000_model_catalog/migration.sql +26 -0
- package/prisma/schema.prisma +258 -2
- package/dist/mcp/tools/models.d.ts +0 -8
- package/dist/mcp/tools/models.js +0 -15
- package/dist/providers/llm/pricing-anthropic.d.ts +0 -14
- package/dist/providers/llm/pricing-anthropic.js +0 -48
- package/dist/providers/llm/pricing-bedrock-claude.d.ts +0 -20
- package/dist/providers/llm/pricing-bedrock-claude.js +0 -46
- package/dist/providers/llm/pricing.d.ts +0 -30
- package/dist/providers/llm/pricing.js +0 -74
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* is imported only here; all protocol/pricing-dispatch logic lives in the
|
|
4
4
|
* shared ClaudeLlmProvider base class (claude-provider.ts) — AnthropicBedrock
|
|
5
5
|
* exposes the identical .messages.stream() surface as @anthropic-ai/sdk, so
|
|
6
|
-
* this file's only job is constructing the right client
|
|
6
|
+
* this file's only job is constructing the right client.
|
|
7
7
|
*
|
|
8
8
|
* Named BedrockClaudeLlmProvider, not BedrockLlmProvider: Bedrock hosts
|
|
9
9
|
* multiple model vendors, and this adapter only ever speaks the
|
|
@@ -12,8 +12,8 @@
|
|
|
12
12
|
*/
|
|
13
13
|
import { AnthropicBedrock } from "@anthropic-ai/bedrock-sdk";
|
|
14
14
|
import { ClaudeLlmProvider } from "./claude-provider.js";
|
|
15
|
-
|
|
15
|
+
import type { CatalogLookup } from "./catalog-lookup.js";
|
|
16
16
|
export declare function bedrockCredentialsPresent(env?: NodeJS.ProcessEnv): boolean;
|
|
17
17
|
export declare class BedrockClaudeLlmProvider extends ClaudeLlmProvider {
|
|
18
|
-
constructor(region?: string, client?: AnthropicBedrock);
|
|
18
|
+
constructor(region?: string, client?: AnthropicBedrock, lookup?: CatalogLookup);
|
|
19
19
|
}
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* is imported only here; all protocol/pricing-dispatch logic lives in the
|
|
4
4
|
* shared ClaudeLlmProvider base class (claude-provider.ts) — AnthropicBedrock
|
|
5
5
|
* exposes the identical .messages.stream() surface as @anthropic-ai/sdk, so
|
|
6
|
-
* this file's only job is constructing the right client
|
|
6
|
+
* this file's only job is constructing the right client.
|
|
7
7
|
*
|
|
8
8
|
* Named BedrockClaudeLlmProvider, not BedrockLlmProvider: Bedrock hosts
|
|
9
9
|
* multiple model vendors, and this adapter only ever speaks the
|
|
@@ -12,8 +12,6 @@
|
|
|
12
12
|
*/
|
|
13
13
|
import { AnthropicBedrock } from "@anthropic-ai/bedrock-sdk";
|
|
14
14
|
import { ClaudeLlmProvider } from "./claude-provider.js";
|
|
15
|
-
import { bedrockClaudePriceUsd, bedrockClaudeSupportedEfforts, getBedrockClaudePricing, } from "./pricing-bedrock-claude.js";
|
|
16
|
-
export { bedrockClaudeSupportedModels } from "./pricing-bedrock-claude.js";
|
|
17
15
|
export function bedrockCredentialsPresent(env = process.env) {
|
|
18
16
|
// A region is the one thing Bedrock always needs; AWS resolves actual
|
|
19
17
|
// credentials from the standard default chain (env keys, shared config,
|
|
@@ -21,14 +19,10 @@ export function bedrockCredentialsPresent(env = process.env) {
|
|
|
21
19
|
return Boolean(env.BEDROCK_REGION ?? env.AWS_REGION);
|
|
22
20
|
}
|
|
23
21
|
export class BedrockClaudeLlmProvider extends ClaudeLlmProvider {
|
|
24
|
-
constructor(region = process.env.BEDROCK_REGION ?? process.env.AWS_REGION ?? "", client) {
|
|
22
|
+
constructor(region = process.env.BEDROCK_REGION ?? process.env.AWS_REGION ?? "", client, lookup) {
|
|
25
23
|
if (!client && !region) {
|
|
26
24
|
throw new Error("BEDROCK_REGION (or AWS_REGION) is required by the Bedrock Claude LlmProvider adapter.");
|
|
27
25
|
}
|
|
28
|
-
super(client ?? new AnthropicBedrock({ awsRegion: region }),
|
|
29
|
-
getPricing: getBedrockClaudePricing,
|
|
30
|
-
priceUsd: bedrockClaudePriceUsd,
|
|
31
|
-
supportedEfforts: bedrockClaudeSupportedEfforts,
|
|
32
|
-
});
|
|
26
|
+
super(client ?? new AnthropicBedrock({ awsRegion: region }), lookup);
|
|
33
27
|
}
|
|
34
28
|
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How an adapter finds the catalog entry for a model. The default reads the
|
|
3
|
+
* process's current catalog; pinnedLookup binds an adapter to one run's
|
|
4
|
+
* stored entry. Kept apart from the store so adapters depend only on this.
|
|
5
|
+
*/
|
|
6
|
+
import { type CatalogEntry } from "./catalog-types.js";
|
|
7
|
+
/** Throws ModelUnavailableError for a model it does not serve. */
|
|
8
|
+
export type CatalogLookup = (model: string) => CatalogEntry;
|
|
9
|
+
export declare const currentLookup: CatalogLookup;
|
|
10
|
+
export declare function pinnedLookup(entry: CatalogEntry): CatalogLookup;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How an adapter finds the catalog entry for a model. The default reads the
|
|
3
|
+
* process's current catalog; pinnedLookup binds an adapter to one run's
|
|
4
|
+
* stored entry. Kept apart from the store so adapters depend only on this.
|
|
5
|
+
*/
|
|
6
|
+
import { ModelUnavailableError } from "./catalog-types.js";
|
|
7
|
+
import { currentModelCatalog } from "./catalog-store.js";
|
|
8
|
+
export const currentLookup = (model) => currentModelCatalog().require(model);
|
|
9
|
+
export function pinnedLookup(entry) {
|
|
10
|
+
return (model) => {
|
|
11
|
+
if (model !== entry.modelId)
|
|
12
|
+
throw new ModelUnavailableError(model, "not_in_catalog");
|
|
13
|
+
return entry;
|
|
14
|
+
};
|
|
15
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The model catalog wardby ships. Every entry is fully literal: rates are the
|
|
3
|
+
* provider's own published numbers for that exact model, never derived from
|
|
4
|
+
* another field (CLAUDE.md "LLM pricing tables — STRICT"). A deployment's
|
|
5
|
+
* admins override or extend this with ModelCatalogEntry rows (set_model);
|
|
6
|
+
* this file changes only with a wardby release, and SHIPPED_CATALOG_VERSION
|
|
7
|
+
* must be bumped whenever any value here changes.
|
|
8
|
+
*
|
|
9
|
+
* Anthropic and Bedrock rates are the 5-minute cache-TTL numbers
|
|
10
|
+
* (platform.claude.com/docs/en/about-claude/pricing): wardby only emits the
|
|
11
|
+
* default 5-minute cache_control breakpoint. OpenAI's pre-GPT-5.6 models have
|
|
12
|
+
* no distinct cache-write tier (a first-time prefix bills at the standard
|
|
13
|
+
* input rate), so their cacheWritePerMTok is their own input rate, stated
|
|
14
|
+
* literally.
|
|
15
|
+
*/
|
|
16
|
+
import type { CatalogEntry } from "./catalog-types.js";
|
|
17
|
+
export declare const SHIPPED_CATALOG_VERSION = "2026-10-03";
|
|
18
|
+
export declare const SHIPPED_CATALOG: readonly CatalogEntry[];
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
export const SHIPPED_CATALOG_VERSION = "2026-10-03";
|
|
2
|
+
const ALL_EFFORTS = ["low", "medium", "high", "xhigh", "max"];
|
|
3
|
+
export const SHIPPED_CATALOG = [
|
|
4
|
+
// OpenAI
|
|
5
|
+
{
|
|
6
|
+
provider: "openai",
|
|
7
|
+
modelId: "gpt-4o",
|
|
8
|
+
encoding: "o200k_base",
|
|
9
|
+
inputPerMTok: 2.5,
|
|
10
|
+
outputPerMTok: 10.0,
|
|
11
|
+
cachedInputPerMTok: 1.25,
|
|
12
|
+
cacheWritePerMTok: 2.5,
|
|
13
|
+
efforts: [],
|
|
14
|
+
thinkingMode: "none",
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
provider: "openai",
|
|
18
|
+
modelId: "gpt-4o-mini",
|
|
19
|
+
encoding: "o200k_base",
|
|
20
|
+
inputPerMTok: 0.15,
|
|
21
|
+
outputPerMTok: 0.6,
|
|
22
|
+
cachedInputPerMTok: 0.075,
|
|
23
|
+
cacheWritePerMTok: 0.15,
|
|
24
|
+
efforts: [],
|
|
25
|
+
thinkingMode: "none",
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
provider: "openai",
|
|
29
|
+
modelId: "gpt-4.1",
|
|
30
|
+
encoding: "o200k_base",
|
|
31
|
+
inputPerMTok: 2.0,
|
|
32
|
+
outputPerMTok: 8.0,
|
|
33
|
+
cachedInputPerMTok: 0.5,
|
|
34
|
+
cacheWritePerMTok: 2.0,
|
|
35
|
+
efforts: [],
|
|
36
|
+
thinkingMode: "none",
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
provider: "openai",
|
|
40
|
+
modelId: "gpt-4.1-mini",
|
|
41
|
+
encoding: "o200k_base",
|
|
42
|
+
inputPerMTok: 0.4,
|
|
43
|
+
outputPerMTok: 1.6,
|
|
44
|
+
cachedInputPerMTok: 0.1,
|
|
45
|
+
cacheWritePerMTok: 0.4,
|
|
46
|
+
efforts: [],
|
|
47
|
+
thinkingMode: "none",
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
provider: "openai",
|
|
51
|
+
modelId: "gpt-4.1-nano",
|
|
52
|
+
encoding: "o200k_base",
|
|
53
|
+
inputPerMTok: 0.1,
|
|
54
|
+
outputPerMTok: 0.4,
|
|
55
|
+
cachedInputPerMTok: 0.025,
|
|
56
|
+
cacheWritePerMTok: 0.1,
|
|
57
|
+
efforts: [],
|
|
58
|
+
thinkingMode: "none",
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
provider: "openai",
|
|
62
|
+
modelId: "gpt-6-astra",
|
|
63
|
+
encoding: "o200k_base",
|
|
64
|
+
inputPerMTok: 10.0,
|
|
65
|
+
outputPerMTok: 50.0,
|
|
66
|
+
cachedInputPerMTok: 1.0,
|
|
67
|
+
cacheWritePerMTok: 12.5,
|
|
68
|
+
efforts: [],
|
|
69
|
+
thinkingMode: "none",
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
provider: "openai",
|
|
73
|
+
modelId: "gpt-5.6-sol",
|
|
74
|
+
encoding: "o200k_base",
|
|
75
|
+
inputPerMTok: 4.0,
|
|
76
|
+
outputPerMTok: 20.0,
|
|
77
|
+
cachedInputPerMTok: 0.4,
|
|
78
|
+
cacheWritePerMTok: 5.0,
|
|
79
|
+
efforts: [],
|
|
80
|
+
thinkingMode: "none",
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
provider: "openai",
|
|
84
|
+
modelId: "gpt-5.6-terra",
|
|
85
|
+
encoding: "o200k_base",
|
|
86
|
+
inputPerMTok: 2.0,
|
|
87
|
+
outputPerMTok: 12.0,
|
|
88
|
+
cachedInputPerMTok: 0.2,
|
|
89
|
+
cacheWritePerMTok: 2.5,
|
|
90
|
+
efforts: [],
|
|
91
|
+
thinkingMode: "none",
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
provider: "openai",
|
|
95
|
+
modelId: "gpt-5.6-luna",
|
|
96
|
+
encoding: "o200k_base",
|
|
97
|
+
inputPerMTok: 0.2,
|
|
98
|
+
outputPerMTok: 1.2,
|
|
99
|
+
cachedInputPerMTok: 0.02,
|
|
100
|
+
cacheWritePerMTok: 0.25,
|
|
101
|
+
efforts: [],
|
|
102
|
+
thinkingMode: "none",
|
|
103
|
+
},
|
|
104
|
+
// Anthropic (direct API)
|
|
105
|
+
{
|
|
106
|
+
provider: "anthropic",
|
|
107
|
+
modelId: "claude-opus-5",
|
|
108
|
+
encoding: "o200k_base",
|
|
109
|
+
inputPerMTok: 5,
|
|
110
|
+
outputPerMTok: 25,
|
|
111
|
+
cachedInputPerMTok: 0.5,
|
|
112
|
+
cacheWritePerMTok: 6.25,
|
|
113
|
+
efforts: ALL_EFFORTS,
|
|
114
|
+
thinkingMode: "adaptive",
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
provider: "anthropic",
|
|
118
|
+
modelId: "claude-sonnet-5",
|
|
119
|
+
encoding: "o200k_base",
|
|
120
|
+
inputPerMTok: 2,
|
|
121
|
+
outputPerMTok: 10,
|
|
122
|
+
cachedInputPerMTok: 0.2,
|
|
123
|
+
cacheWritePerMTok: 2.5,
|
|
124
|
+
efforts: ALL_EFFORTS,
|
|
125
|
+
thinkingMode: "adaptive",
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
provider: "anthropic",
|
|
129
|
+
modelId: "claude-fable-5",
|
|
130
|
+
encoding: "o200k_base",
|
|
131
|
+
inputPerMTok: 10,
|
|
132
|
+
outputPerMTok: 50,
|
|
133
|
+
cachedInputPerMTok: 1,
|
|
134
|
+
cacheWritePerMTok: 12.5,
|
|
135
|
+
efforts: ALL_EFFORTS,
|
|
136
|
+
thinkingMode: "adaptive",
|
|
137
|
+
},
|
|
138
|
+
// Haiku 4.5 rejects adaptive thinking and any effort level.
|
|
139
|
+
{
|
|
140
|
+
provider: "anthropic",
|
|
141
|
+
modelId: "claude-haiku-4-5",
|
|
142
|
+
encoding: "o200k_base",
|
|
143
|
+
inputPerMTok: 1,
|
|
144
|
+
outputPerMTok: 5,
|
|
145
|
+
cachedInputPerMTok: 0.1,
|
|
146
|
+
cacheWritePerMTok: 1.25,
|
|
147
|
+
efforts: [],
|
|
148
|
+
thinkingMode: "manual",
|
|
149
|
+
},
|
|
150
|
+
// Bedrock-Claude (`us.` cross-region inference profiles). Effort is never sent
|
|
151
|
+
// through Bedrock (unconfirmed per model), and wardby sends Bedrock no thinking
|
|
152
|
+
// parameter at all, so thinkingMode is inert here; "adaptive" by convention.
|
|
153
|
+
{
|
|
154
|
+
provider: "bedrock-claude",
|
|
155
|
+
modelId: "us.anthropic.claude-sonnet-4-6",
|
|
156
|
+
encoding: "o200k_base",
|
|
157
|
+
inputPerMTok: 3,
|
|
158
|
+
outputPerMTok: 15,
|
|
159
|
+
cachedInputPerMTok: 0.3,
|
|
160
|
+
cacheWritePerMTok: 3.75,
|
|
161
|
+
efforts: [],
|
|
162
|
+
thinkingMode: "adaptive",
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
provider: "bedrock-claude",
|
|
166
|
+
modelId: "us.anthropic.claude-opus-4-6-v1",
|
|
167
|
+
encoding: "o200k_base",
|
|
168
|
+
inputPerMTok: 5,
|
|
169
|
+
outputPerMTok: 25,
|
|
170
|
+
cachedInputPerMTok: 0.5,
|
|
171
|
+
cacheWritePerMTok: 6.25,
|
|
172
|
+
efforts: [],
|
|
173
|
+
thinkingMode: "adaptive",
|
|
174
|
+
},
|
|
175
|
+
{
|
|
176
|
+
provider: "bedrock-claude",
|
|
177
|
+
modelId: "us.anthropic.claude-opus-4-8",
|
|
178
|
+
encoding: "o200k_base",
|
|
179
|
+
inputPerMTok: 5,
|
|
180
|
+
outputPerMTok: 25,
|
|
181
|
+
cachedInputPerMTok: 0.5,
|
|
182
|
+
cacheWritePerMTok: 6.25,
|
|
183
|
+
efforts: [],
|
|
184
|
+
thinkingMode: "adaptive",
|
|
185
|
+
},
|
|
186
|
+
{
|
|
187
|
+
provider: "bedrock-claude",
|
|
188
|
+
modelId: "us.anthropic.claude-haiku-4-5-20251001-v1:0",
|
|
189
|
+
encoding: "o200k_base",
|
|
190
|
+
inputPerMTok: 1,
|
|
191
|
+
outputPerMTok: 5,
|
|
192
|
+
cachedInputPerMTok: 0.1,
|
|
193
|
+
cacheWritePerMTok: 1.25,
|
|
194
|
+
efforts: [],
|
|
195
|
+
thinkingMode: "adaptive",
|
|
196
|
+
},
|
|
197
|
+
];
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { type ModelCatalog } from "./catalog.js";
|
|
2
|
+
export interface CatalogDb {
|
|
3
|
+
modelCatalogEntry: {
|
|
4
|
+
findMany(): Promise<Record<string, unknown>[]>;
|
|
5
|
+
};
|
|
6
|
+
}
|
|
7
|
+
export interface CatalogLog {
|
|
8
|
+
warn(payload: Record<string, unknown>, message: string): void;
|
|
9
|
+
info(payload: Record<string, unknown>, message: string): void;
|
|
10
|
+
}
|
|
11
|
+
export declare function refreshIntervalMs(env?: NodeJS.ProcessEnv): number;
|
|
12
|
+
export declare class CatalogStore {
|
|
13
|
+
private readonly db;
|
|
14
|
+
private catalog;
|
|
15
|
+
private timer;
|
|
16
|
+
private readonly intervalMs;
|
|
17
|
+
private readonly log;
|
|
18
|
+
/** Monotonic id handed out per load attempt (poll or refreshNow), taken before the
|
|
19
|
+
* awaited fetch starts so loads can be ordered by when they were *initiated*, not
|
|
20
|
+
* when they happen to resolve. */
|
|
21
|
+
private loadSeq;
|
|
22
|
+
/** The seq of the load currently reflected in `catalog`. A load whose result arrives
|
|
23
|
+
* after a newer one already applied is discarded — see `applyLoad`. */
|
|
24
|
+
private appliedSeq;
|
|
25
|
+
/** True once `close()` has run; a load already in flight at that point must not
|
|
26
|
+
* assign into `catalog` or log on failure when it eventually settles. */
|
|
27
|
+
private closed;
|
|
28
|
+
/** True while a poll-triggered load is in flight, so a slow load doesn't overlap
|
|
29
|
+
* with the next interval tick. */
|
|
30
|
+
private polling;
|
|
31
|
+
constructor(db: CatalogDb, options?: {
|
|
32
|
+
intervalMs?: number;
|
|
33
|
+
log?: CatalogLog;
|
|
34
|
+
});
|
|
35
|
+
start(): Promise<void>;
|
|
36
|
+
current(): ModelCatalog;
|
|
37
|
+
refreshNow(): Promise<void>;
|
|
38
|
+
close(): void;
|
|
39
|
+
/** Runs one load, then applies it only if it is still the newest load to have
|
|
40
|
+
* started and the store hasn't been closed in the meantime. Rejects (without
|
|
41
|
+
* touching `catalog`) if the underlying load fails. */
|
|
42
|
+
private applyLoad;
|
|
43
|
+
private load;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Starts a store against `db`, installs it as what `currentModelCatalog()`
|
|
47
|
+
* returns process-wide, and returns it. Throws (via `CatalogStore.start()`)
|
|
48
|
+
* if the database is unreachable — every process that calls this fails
|
|
49
|
+
* fast at startup rather than silently running on the shipped catalog
|
|
50
|
+
* alone, which would quietly re-enable models an admin turned off.
|
|
51
|
+
*/
|
|
52
|
+
export declare function startModelCatalog(db: CatalogDb, options?: {
|
|
53
|
+
intervalMs?: number;
|
|
54
|
+
log?: CatalogLog;
|
|
55
|
+
}): Promise<CatalogStore>;
|
|
56
|
+
export declare function installModelCatalog(store: CatalogStore): void;
|
|
57
|
+
export declare function currentModelCatalog(): ModelCatalog;
|
|
58
|
+
export declare function uninstallModelCatalogForTests(): void;
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Holds this process's merged model catalog. Loads it at startup (failing
|
|
3
|
+
* the process if the database is unreachable: running on the shipped catalog
|
|
4
|
+
* alone would quietly re-enable models an admin turned off), then polls
|
|
5
|
+
* ModelCatalogEntry every WARDBY_MODEL_CATALOG_REFRESH_SECONDS (default 45)
|
|
6
|
+
* and swaps in a new immutable catalog. A failed poll keeps the last good
|
|
7
|
+
* catalog. current() is synchronous: per-token pricing reads it.
|
|
8
|
+
*
|
|
9
|
+
* One store per process (serve, mcp, scheduler); installModelCatalog makes
|
|
10
|
+
* it what currentModelCatalog() returns everywhere. Before a store is
|
|
11
|
+
* installed (unit tests, offline CLI commands) currentModelCatalog() is the
|
|
12
|
+
* shipped catalog.
|
|
13
|
+
*/
|
|
14
|
+
import { logger } from "../../core/logger.js";
|
|
15
|
+
import { buildCatalog, rowFromRecord, shippedCatalog } from "./catalog.js";
|
|
16
|
+
import { SHIPPED_CATALOG, SHIPPED_CATALOG_VERSION } from "./catalog-shipped.js";
|
|
17
|
+
const DEFAULT_REFRESH_SECONDS = 45;
|
|
18
|
+
export function refreshIntervalMs(env = process.env) {
|
|
19
|
+
const raw = env.WARDBY_MODEL_CATALOG_REFRESH_SECONDS;
|
|
20
|
+
if (raw === undefined || raw === "")
|
|
21
|
+
return DEFAULT_REFRESH_SECONDS * 1000;
|
|
22
|
+
if (!/^[1-9]\d*$/.test(raw)) {
|
|
23
|
+
throw new Error(`WARDBY_MODEL_CATALOG_REFRESH_SECONDS must be a positive whole number of seconds (got "${raw}").`);
|
|
24
|
+
}
|
|
25
|
+
return Number(raw) * 1000;
|
|
26
|
+
}
|
|
27
|
+
export class CatalogStore {
|
|
28
|
+
db;
|
|
29
|
+
catalog = shippedCatalog();
|
|
30
|
+
timer;
|
|
31
|
+
intervalMs;
|
|
32
|
+
log;
|
|
33
|
+
/** Monotonic id handed out per load attempt (poll or refreshNow), taken before the
|
|
34
|
+
* awaited fetch starts so loads can be ordered by when they were *initiated*, not
|
|
35
|
+
* when they happen to resolve. */
|
|
36
|
+
loadSeq = 0;
|
|
37
|
+
/** The seq of the load currently reflected in `catalog`. A load whose result arrives
|
|
38
|
+
* after a newer one already applied is discarded — see `applyLoad`. */
|
|
39
|
+
appliedSeq = 0;
|
|
40
|
+
/** True once `close()` has run; a load already in flight at that point must not
|
|
41
|
+
* assign into `catalog` or log on failure when it eventually settles. */
|
|
42
|
+
closed = false;
|
|
43
|
+
/** True while a poll-triggered load is in flight, so a slow load doesn't overlap
|
|
44
|
+
* with the next interval tick. */
|
|
45
|
+
polling = false;
|
|
46
|
+
constructor(db, options = {}) {
|
|
47
|
+
this.db = db;
|
|
48
|
+
this.intervalMs = options.intervalMs ?? refreshIntervalMs();
|
|
49
|
+
this.log = options.log ?? logger.child({ module: "model-catalog" });
|
|
50
|
+
}
|
|
51
|
+
async start() {
|
|
52
|
+
if (this.timer) {
|
|
53
|
+
clearInterval(this.timer);
|
|
54
|
+
this.timer = undefined;
|
|
55
|
+
}
|
|
56
|
+
this.closed = false;
|
|
57
|
+
try {
|
|
58
|
+
await this.applyLoad();
|
|
59
|
+
}
|
|
60
|
+
catch (err) {
|
|
61
|
+
throw new Error(`Could not load the model catalog from the database: ${err instanceof Error ? err.message : String(err)}`, { cause: err });
|
|
62
|
+
}
|
|
63
|
+
this.timer = setInterval(() => {
|
|
64
|
+
if (this.polling)
|
|
65
|
+
return;
|
|
66
|
+
this.polling = true;
|
|
67
|
+
this.applyLoad().then(() => {
|
|
68
|
+
this.polling = false;
|
|
69
|
+
}, (err) => {
|
|
70
|
+
this.polling = false;
|
|
71
|
+
if (this.closed)
|
|
72
|
+
return;
|
|
73
|
+
this.log.warn({ event: "models.catalog.refresh_failed", err: err instanceof Error ? err.message : String(err) }, "model catalog refresh failed; keeping the last good catalog");
|
|
74
|
+
});
|
|
75
|
+
}, this.intervalMs);
|
|
76
|
+
this.timer.unref?.();
|
|
77
|
+
}
|
|
78
|
+
current() {
|
|
79
|
+
return this.catalog;
|
|
80
|
+
}
|
|
81
|
+
async refreshNow() {
|
|
82
|
+
await this.applyLoad();
|
|
83
|
+
}
|
|
84
|
+
close() {
|
|
85
|
+
this.closed = true;
|
|
86
|
+
if (this.timer)
|
|
87
|
+
clearInterval(this.timer);
|
|
88
|
+
this.timer = undefined;
|
|
89
|
+
}
|
|
90
|
+
/** Runs one load, then applies it only if it is still the newest load to have
|
|
91
|
+
* started and the store hasn't been closed in the meantime. Rejects (without
|
|
92
|
+
* touching `catalog`) if the underlying load fails. */
|
|
93
|
+
async applyLoad() {
|
|
94
|
+
const seq = ++this.loadSeq;
|
|
95
|
+
const next = await this.load();
|
|
96
|
+
if (this.closed)
|
|
97
|
+
return;
|
|
98
|
+
if (seq > this.appliedSeq) {
|
|
99
|
+
this.catalog = next;
|
|
100
|
+
this.appliedSeq = seq;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
async load() {
|
|
104
|
+
const records = await this.db.modelCatalogEntry.findMany();
|
|
105
|
+
const rows = [];
|
|
106
|
+
for (const record of records) {
|
|
107
|
+
const row = rowFromRecord(record);
|
|
108
|
+
if (row)
|
|
109
|
+
rows.push(row);
|
|
110
|
+
else
|
|
111
|
+
this.log.warn({ event: "models.catalog.row_invalid", provider: record.provider, modelId: record.modelId }, "skipping a malformed ModelCatalogEntry row");
|
|
112
|
+
}
|
|
113
|
+
return buildCatalog(SHIPPED_CATALOG, rows, SHIPPED_CATALOG_VERSION, (message) => this.log.warn({ event: "models.catalog.conflict" }, message));
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Starts a store against `db`, installs it as what `currentModelCatalog()`
|
|
118
|
+
* returns process-wide, and returns it. Throws (via `CatalogStore.start()`)
|
|
119
|
+
* if the database is unreachable — every process that calls this fails
|
|
120
|
+
* fast at startup rather than silently running on the shipped catalog
|
|
121
|
+
* alone, which would quietly re-enable models an admin turned off.
|
|
122
|
+
*/
|
|
123
|
+
export async function startModelCatalog(db, options = {}) {
|
|
124
|
+
const store = new CatalogStore(db, options);
|
|
125
|
+
await store.start();
|
|
126
|
+
installModelCatalog(store);
|
|
127
|
+
return store;
|
|
128
|
+
}
|
|
129
|
+
let installed;
|
|
130
|
+
export function installModelCatalog(store) {
|
|
131
|
+
installed = store;
|
|
132
|
+
}
|
|
133
|
+
export function currentModelCatalog() {
|
|
134
|
+
return installed?.current() ?? shippedCatalog();
|
|
135
|
+
}
|
|
136
|
+
export function uninstallModelCatalogForTests() {
|
|
137
|
+
installed = undefined;
|
|
138
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The model catalog's shapes. A CatalogEntry is everything wardby needs to
|
|
3
|
+
* route, price, tokenize and shape requests for one model; every field is
|
|
4
|
+
* required (CLAUDE.md "LLM pricing tables — STRICT": cache rates are never
|
|
5
|
+
* omitted and never derived).
|
|
6
|
+
*/
|
|
7
|
+
import type { TokenizerEncoding } from "./pricing-core.js";
|
|
8
|
+
import { type LlmEffort } from "./types.js";
|
|
9
|
+
export declare const MODEL_PROVIDERS: readonly ["openai", "anthropic", "bedrock-claude"];
|
|
10
|
+
export type ModelProvider = (typeof MODEL_PROVIDERS)[number];
|
|
11
|
+
/**
|
|
12
|
+
* How a Claude model takes extended thinking. adaptive: `{type: "adaptive"}`
|
|
13
|
+
* plus an effort level. manual: `{type: "enabled", budget_tokens}` and no
|
|
14
|
+
* effort (Claude Haiku 4.5 returns a 400 for adaptive). none: no thinking
|
|
15
|
+
* parameter (OpenAI models).
|
|
16
|
+
*/
|
|
17
|
+
export declare const THINKING_MODES: readonly ["adaptive", "manual", "none"];
|
|
18
|
+
export type ThinkingMode = (typeof THINKING_MODES)[number];
|
|
19
|
+
export declare const TOKENIZER_ENCODINGS: readonly ["cl100k_base", "o200k_base"];
|
|
20
|
+
export interface CatalogEntry {
|
|
21
|
+
provider: ModelProvider;
|
|
22
|
+
/** Exact string an Agent.model must equal. */
|
|
23
|
+
modelId: string;
|
|
24
|
+
encoding: TokenizerEncoding;
|
|
25
|
+
inputPerMTok: number;
|
|
26
|
+
outputPerMTok: number;
|
|
27
|
+
cachedInputPerMTok: number;
|
|
28
|
+
cacheWritePerMTok: number;
|
|
29
|
+
/** Effort levels the model accepts; empty = never send one. */
|
|
30
|
+
efforts: readonly LlmEffort[];
|
|
31
|
+
thinkingMode: ThinkingMode;
|
|
32
|
+
}
|
|
33
|
+
/** An entry as the merged catalog serves it. */
|
|
34
|
+
export interface ResolvedCatalogEntry extends CatalogEntry {
|
|
35
|
+
origin: "shipped" | "override";
|
|
36
|
+
/** "shipped:<SHIPPED_CATALOG_VERSION>" or the override row's updatedAt (ISO). */
|
|
37
|
+
priceVersion: string;
|
|
38
|
+
/** Overrides of a shipped model only: whether the shipped entry now differs. */
|
|
39
|
+
shippedDiffers?: boolean;
|
|
40
|
+
/** Overrides only. */
|
|
41
|
+
sourceUrl?: string;
|
|
42
|
+
updatedBy?: string;
|
|
43
|
+
updatedAt?: Date;
|
|
44
|
+
}
|
|
45
|
+
/** A ModelCatalogEntry row, validated. */
|
|
46
|
+
export interface CatalogRow extends CatalogEntry {
|
|
47
|
+
enabled: boolean;
|
|
48
|
+
sourceUrl: string;
|
|
49
|
+
updatedBy: string;
|
|
50
|
+
/** When the row was first written: decides which provider owns a non-shipped id (ownerOf). */
|
|
51
|
+
createdAt: Date;
|
|
52
|
+
updatedAt: Date;
|
|
53
|
+
}
|
|
54
|
+
export type ModelUnavailableReason = "not_in_catalog" | "disabled" | "provider_not_configured";
|
|
55
|
+
/** A model the deployment cannot run. Thrown before any spend. */
|
|
56
|
+
export declare class ModelUnavailableError extends Error {
|
|
57
|
+
readonly modelId: string;
|
|
58
|
+
readonly reason: ModelUnavailableReason;
|
|
59
|
+
readonly code: "model_unavailable";
|
|
60
|
+
constructor(modelId: string, reason: ModelUnavailableReason);
|
|
61
|
+
}
|
|
62
|
+
/** Strips a resolved entry down to the stored snapshot shape. */
|
|
63
|
+
export declare function entryOf(entry: CatalogEntry): CatalogEntry;
|
|
64
|
+
export declare function sameEntry(a: CatalogEntry, b: CatalogEntry): boolean;
|
|
65
|
+
/** Parses a stored snapshot (Run.pricingSnapshot, CodingProxySession.catalogEntry); null if absent or malformed. */
|
|
66
|
+
export declare function parseStoredEntry(value: unknown): CatalogEntry | null;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { isLlmEffort } from "./types.js";
|
|
2
|
+
export const MODEL_PROVIDERS = ["openai", "anthropic", "bedrock-claude"];
|
|
3
|
+
/**
|
|
4
|
+
* How a Claude model takes extended thinking. adaptive: `{type: "adaptive"}`
|
|
5
|
+
* plus an effort level. manual: `{type: "enabled", budget_tokens}` and no
|
|
6
|
+
* effort (Claude Haiku 4.5 returns a 400 for adaptive). none: no thinking
|
|
7
|
+
* parameter (OpenAI models).
|
|
8
|
+
*/
|
|
9
|
+
export const THINKING_MODES = ["adaptive", "manual", "none"];
|
|
10
|
+
export const TOKENIZER_ENCODINGS = ["cl100k_base", "o200k_base"];
|
|
11
|
+
const REASON_TEXT = {
|
|
12
|
+
not_in_catalog: "is not in this deployment's model catalog",
|
|
13
|
+
disabled: "is disabled in this deployment's model catalog",
|
|
14
|
+
provider_not_configured: "belongs to a provider with no credentials configured in this deployment",
|
|
15
|
+
};
|
|
16
|
+
/** A model the deployment cannot run. Thrown before any spend. */
|
|
17
|
+
export class ModelUnavailableError extends Error {
|
|
18
|
+
modelId;
|
|
19
|
+
reason;
|
|
20
|
+
code = "model_unavailable";
|
|
21
|
+
constructor(modelId, reason) {
|
|
22
|
+
super(`model_unavailable: Model "${modelId}" ${REASON_TEXT[reason]} (reason: ${reason}). See list_models.`);
|
|
23
|
+
this.modelId = modelId;
|
|
24
|
+
this.reason = reason;
|
|
25
|
+
this.name = "ModelUnavailableError";
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
/** Strips a resolved entry down to the stored snapshot shape. */
|
|
29
|
+
export function entryOf(entry) {
|
|
30
|
+
return {
|
|
31
|
+
provider: entry.provider,
|
|
32
|
+
modelId: entry.modelId,
|
|
33
|
+
encoding: entry.encoding,
|
|
34
|
+
inputPerMTok: entry.inputPerMTok,
|
|
35
|
+
outputPerMTok: entry.outputPerMTok,
|
|
36
|
+
cachedInputPerMTok: entry.cachedInputPerMTok,
|
|
37
|
+
cacheWritePerMTok: entry.cacheWritePerMTok,
|
|
38
|
+
efforts: [...entry.efforts],
|
|
39
|
+
thinkingMode: entry.thinkingMode,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
export function sameEntry(a, b) {
|
|
43
|
+
return JSON.stringify(entryOf(a)) === JSON.stringify(entryOf(b));
|
|
44
|
+
}
|
|
45
|
+
const rate = (v) => typeof v === "number" && Number.isFinite(v) && v >= 0;
|
|
46
|
+
/** Parses a stored snapshot (Run.pricingSnapshot, CodingProxySession.catalogEntry); null if absent or malformed. */
|
|
47
|
+
export function parseStoredEntry(value) {
|
|
48
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
49
|
+
return null;
|
|
50
|
+
const v = value;
|
|
51
|
+
if (!MODEL_PROVIDERS.includes(v.provider))
|
|
52
|
+
return null;
|
|
53
|
+
if (typeof v.modelId !== "string" || v.modelId.length === 0)
|
|
54
|
+
return null;
|
|
55
|
+
if (!TOKENIZER_ENCODINGS.includes(v.encoding))
|
|
56
|
+
return null;
|
|
57
|
+
if (![v.inputPerMTok, v.outputPerMTok, v.cachedInputPerMTok, v.cacheWritePerMTok].every(rate))
|
|
58
|
+
return null;
|
|
59
|
+
if (!Array.isArray(v.efforts) || !v.efforts.every(isLlmEffort))
|
|
60
|
+
return null;
|
|
61
|
+
if (!THINKING_MODES.includes(v.thinkingMode))
|
|
62
|
+
return null;
|
|
63
|
+
return entryOf(v);
|
|
64
|
+
}
|