@orthacms/copilot-provider-anthropic 0.0.0-reserve.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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Ortha CMS contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,7 @@
1
+ # @orthacms/copilot-provider-anthropic
2
+
3
+ Part of [Ortha CMS](https://github.com/ortha-source/ortha-cms).
4
+
5
+ ```sh
6
+ npm install @orthacms/copilot-provider-anthropic
7
+ ```
@@ -0,0 +1,3 @@
1
+ export { createAnthropicProvider } from './lib/anthropic-provider';
2
+ export type { AnthropicProviderConfig, AnthropicEffort } from './lib/config';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,uBAAuB,EAAE,MAAM,0BAA0B,CAAC;AACnE,YAAY,EAAE,uBAAuB,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,5 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createAnthropicProvider = void 0;
4
+ var anthropic_provider_1 = require("./lib/anthropic-provider");
5
+ Object.defineProperty(exports, "createAnthropicProvider", { enumerable: true, get: function () { return anthropic_provider_1.createAnthropicProvider; } });
@@ -0,0 +1,23 @@
1
+ import { type ModelProvider } from '@orthacms/copilot-domain';
2
+ import type { AnthropicProviderConfig } from './config';
3
+ /**
4
+ * Creates the native Claude adapter — the default for tool-heavy work, and the
5
+ * path on which native capabilities stay available
6
+ * ([ADR-0004](../../../../docs/adr/0004-model-agnostic-copilot-provider.md) §3).
7
+ *
8
+ * This file holds only the orchestration; the parts worth reading on their own
9
+ * live beside it — `client.ts` (lazy construction), `capabilities.ts` (the
10
+ * Models API probe), and `wire/` (the two mapping directions).
11
+ *
12
+ * @example
13
+ * ```typescript
14
+ * CopilotPlugin({
15
+ * providers: {
16
+ * anthropic: createAnthropicProvider(config.plugins.copilot.anthropic)
17
+ * },
18
+ * config: config.plugins.copilot
19
+ * });
20
+ * ```
21
+ */
22
+ export declare function createAnthropicProvider(config: AnthropicProviderConfig): ModelProvider;
23
+ //# sourceMappingURL=anthropic-provider.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"anthropic-provider.d.ts","sourceRoot":"","sources":["../../src/lib/anthropic-provider.ts"],"names":[],"mappings":"AAAA,OAAO,EAKH,KAAK,aAAa,EAGrB,MAAM,0BAA0B,CAAC;AAGlC,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,UAAU,CAAC;AAIxD;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,uBAAuB,CACnC,MAAM,EAAE,uBAAuB,GAChC,aAAa,CAsFf"}
@@ -0,0 +1,101 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createAnthropicProvider = createAnthropicProvider;
4
+ const copilot_domain_1 = require("@orthacms/copilot-domain");
5
+ const capabilities_1 = require("./capabilities");
6
+ const client_1 = require("./client");
7
+ const request_1 = require("./wire/request");
8
+ const response_1 = require("./wire/response");
9
+ /**
10
+ * Creates the native Claude adapter — the default for tool-heavy work, and the
11
+ * path on which native capabilities stay available
12
+ * ([ADR-0004](../../../../docs/adr/0004-model-agnostic-copilot-provider.md) §3).
13
+ *
14
+ * This file holds only the orchestration; the parts worth reading on their own
15
+ * live beside it — `client.ts` (lazy construction), `capabilities.ts` (the
16
+ * Models API probe), and `wire/` (the two mapping directions).
17
+ *
18
+ * @example
19
+ * ```typescript
20
+ * CopilotPlugin({
21
+ * providers: {
22
+ * anthropic: createAnthropicProvider(config.plugins.copilot.anthropic)
23
+ * },
24
+ * config: config.plugins.copilot
25
+ * });
26
+ * ```
27
+ */
28
+ function createAnthropicProvider(config) {
29
+ const client = (0, client_1.createLazyClient)(config);
30
+ const models = [...config.models];
31
+ // Cached per model: the probe is one network call per model, and a
32
+ // provider offering three shouldn't pay it three times per run.
33
+ const cachedCapabilities = new Map();
34
+ async function* stream(request, signal) {
35
+ // Resolved before the try: a run naming a model this provider doesn't
36
+ // offer is the caller's error, not an abort to be swallowed.
37
+ const model = (0, copilot_domain_1.resolveModel)(request.model, models);
38
+ // Opening the stream lives inside the try alongside iterating it: an
39
+ // already-aborted signal makes the SDK reject at construction, and
40
+ // that is still an abort rather than a failure the caller should see.
41
+ try {
42
+ const messageStream = client().messages.stream((0, request_1.toStreamParams)(request, config, model), signal ? { signal } : undefined);
43
+ for await (const event of messageStream) {
44
+ if (event.type === 'content_block_delta' &&
45
+ event.delta.type === 'text_delta') {
46
+ yield { type: 'text-delta', text: event.delta.text };
47
+ }
48
+ }
49
+ // Tool calls come from the assembled message rather than from
50
+ // `input_json_delta` fragments, so a call is emitted once, whole,
51
+ // and already parsed — the engine never sees half a call.
52
+ const message = await messageStream.finalMessage();
53
+ for (const block of message.content) {
54
+ if (block.type === 'tool_use') {
55
+ yield {
56
+ type: 'tool-call',
57
+ id: block.id,
58
+ name: block.name,
59
+ input: block.input
60
+ };
61
+ }
62
+ }
63
+ yield {
64
+ type: 'done',
65
+ stopReason: (0, response_1.toStopReason)(message.stop_reason),
66
+ usage: (0, response_1.toUsage)(message.usage)
67
+ };
68
+ }
69
+ catch (error) {
70
+ if ((0, copilot_domain_1.isAbortError)(error, signal)) {
71
+ yield (0, copilot_domain_1.abortedEvent)();
72
+ return;
73
+ }
74
+ throw error;
75
+ }
76
+ }
77
+ return {
78
+ models: () => models,
79
+ capabilities(model) {
80
+ const resolved = (0, copilot_domain_1.resolveModel)(model, models);
81
+ const cached = cachedCapabilities.get(resolved);
82
+ if (cached) {
83
+ return cached;
84
+ }
85
+ // The *promise* is cached, before it resolves, so two concurrent
86
+ // callers await one probe rather than two.
87
+ const probe = (0, capabilities_1.probeCapabilities)(client, resolved).catch(() => {
88
+ // A failed probe is **not** kept: caching it would pin a
89
+ // frontier model to the conservative fallback for the life of
90
+ // the process over one blip — the network comes back and the
91
+ // adapter never notices. Evicting costs at most one probe per
92
+ // call while the endpoint is unreachable.
93
+ cachedCapabilities.delete(resolved);
94
+ return (0, capabilities_1.fallbackCapabilities)(resolved);
95
+ });
96
+ cachedCapabilities.set(resolved, probe);
97
+ return probe;
98
+ },
99
+ stream
100
+ };
101
+ }
@@ -0,0 +1,26 @@
1
+ import type { ModelCapabilities } from '@orthacms/copilot-domain';
2
+ import type { LazyClient } from './client';
3
+ /**
4
+ * Probes the Models API for one model's live capability record.
5
+ *
6
+ * **Rejects** when the call can't be made, rather than returning the fallback
7
+ * itself: the caller caches this promise, and a fallback that resolved
8
+ * successfully would be cached as if it were an answer — pinning a frontier
9
+ * model to the conservative record for the life of the process over one
10
+ * network blip.
11
+ *
12
+ * The Models API exposes no tool-calling flag; every model it serves supports
13
+ * tool calling, so that stays `true` on both paths.
14
+ */
15
+ export declare function probeCapabilities(client: LazyClient, model: string): Promise<ModelCapabilities>;
16
+ /**
17
+ * What one model reports when the probe couldn't run — no network, no key, a
18
+ * gateway that doesn't proxy `/v1/models`.
19
+ *
20
+ * Conservative rather than "unsupported": reporting *unknown* as *unsupported*
21
+ * would drop a frontier model into degraded mode over a transient blip, which
22
+ * is a worse failure than being slightly optimistic about a model whose shape
23
+ * we know.
24
+ */
25
+ export declare function fallbackCapabilities(model: string): ModelCapabilities;
26
+ //# sourceMappingURL=capabilities.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"capabilities.d.ts","sourceRoot":"","sources":["../../src/lib/capabilities.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAClE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAG3C;;;;;;;;;;;GAWG;AACH,wBAAsB,iBAAiB,CACnC,MAAM,EAAE,UAAU,EAClB,KAAK,EAAE,MAAM,GACd,OAAO,CAAC,iBAAiB,CAAC,CAW5B;AAED;;;;;;;;GAQG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,MAAM,GAAG,iBAAiB,CAErE"}
@@ -0,0 +1,41 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.probeCapabilities = probeCapabilities;
4
+ exports.fallbackCapabilities = fallbackCapabilities;
5
+ const config_1 = require("./config");
6
+ /**
7
+ * Probes the Models API for one model's live capability record.
8
+ *
9
+ * **Rejects** when the call can't be made, rather than returning the fallback
10
+ * itself: the caller caches this promise, and a fallback that resolved
11
+ * successfully would be cached as if it were an answer — pinning a frontier
12
+ * model to the conservative record for the life of the process over one
13
+ * network blip.
14
+ *
15
+ * The Models API exposes no tool-calling flag; every model it serves supports
16
+ * tool calling, so that stays `true` on both paths.
17
+ */
18
+ async function probeCapabilities(client, model) {
19
+ const base = fallbackCapabilities(model);
20
+ const info = await client().models.retrieve(model);
21
+ return {
22
+ model: info.id,
23
+ toolCalling: true,
24
+ streaming: true,
25
+ vision: info.capabilities?.image_input.supported ?? base.vision,
26
+ contextWindow: info.max_input_tokens ?? base.contextWindow,
27
+ maxOutputTokens: info.max_tokens ?? base.maxOutputTokens
28
+ };
29
+ }
30
+ /**
31
+ * What one model reports when the probe couldn't run — no network, no key, a
32
+ * gateway that doesn't proxy `/v1/models`.
33
+ *
34
+ * Conservative rather than "unsupported": reporting *unknown* as *unsupported*
35
+ * would drop a frontier model into degraded mode over a transient blip, which
36
+ * is a worse failure than being slightly optimistic about a model whose shape
37
+ * we know.
38
+ */
39
+ function fallbackCapabilities(model) {
40
+ return { ...config_1.FALLBACK_CAPABILITIES, model };
41
+ }
@@ -0,0 +1,15 @@
1
+ import Anthropic from '@anthropic-ai/sdk';
2
+ import type { AnthropicProviderConfig } from './config';
3
+ /** Resolves the SDK client, constructing it on first call. */
4
+ export type LazyClient = () => Anthropic;
5
+ /**
6
+ * Builds a memoized client factory. The SDK client is constructed on **first
7
+ * use**, not up front: a host registers every provider it might route to, but
8
+ * an operator running local inference has no Anthropic key — and an unused
9
+ * adapter must not fail their boot.
10
+ *
11
+ * Selecting the provider without a key then fails at the moment it actually
12
+ * matters, with a message naming the fix.
13
+ */
14
+ export declare function createLazyClient(config: AnthropicProviderConfig): LazyClient;
15
+ //# sourceMappingURL=client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/lib/client.ts"],"names":[],"mappings":"AAAA,OAAO,SAAS,MAAM,mBAAmB,CAAC;AAC1C,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,UAAU,CAAC;AAExD,8DAA8D;AAC9D,MAAM,MAAM,UAAU,GAAG,MAAM,SAAS,CAAC;AAEzC;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,uBAAuB,GAAG,UAAU,CAwB5E"}
@@ -0,0 +1,36 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createLazyClient = createLazyClient;
4
+ const tslib_1 = require("tslib");
5
+ const sdk_1 = tslib_1.__importDefault(require("@anthropic-ai/sdk"));
6
+ /**
7
+ * Builds a memoized client factory. The SDK client is constructed on **first
8
+ * use**, not up front: a host registers every provider it might route to, but
9
+ * an operator running local inference has no Anthropic key — and an unused
10
+ * adapter must not fail their boot.
11
+ *
12
+ * Selecting the provider without a key then fails at the moment it actually
13
+ * matters, with a message naming the fix.
14
+ */
15
+ function createLazyClient(config) {
16
+ let client;
17
+ return () => {
18
+ if (!client) {
19
+ if (!config.apiKey) {
20
+ throw new Error('The Anthropic copilot provider was selected but no API key is configured. ' +
21
+ 'Set ANTHROPIC_API_KEY, or pick another provider in the model picker.');
22
+ }
23
+ client = new sdk_1.default({
24
+ apiKey: config.apiKey,
25
+ ...(config.baseUrl ? { baseURL: config.baseUrl } : {}),
26
+ ...(config.maxRetries === undefined
27
+ ? {}
28
+ : { maxRetries: config.maxRetries }),
29
+ ...(config.timeoutMs === undefined
30
+ ? {}
31
+ : { timeout: config.timeoutMs })
32
+ });
33
+ }
34
+ return client;
35
+ };
36
+ }
@@ -0,0 +1,51 @@
1
+ /**
2
+ * How much of the model's reasoning budget a run may spend. Higher settings
3
+ * trade tokens and latency for depth; `high` is the API default.
4
+ */
5
+ export type AnthropicEffort = 'low' | 'medium' | 'high' | 'xhigh' | 'max';
6
+ /** Options for `createAnthropicProvider`. */
7
+ export interface AnthropicProviderConfig {
8
+ /** API key. Never leaves the server, and never reaches the browser. */
9
+ apiKey: string;
10
+ /**
11
+ * Model ids this provider offers, in preference order; the first is the
12
+ * default. Declaring several — a frontier model for hard work, a cheaper
13
+ * one for routine turns — lets a user switch mid-conversation without a
14
+ * redeploy.
15
+ */
16
+ models: readonly string[];
17
+ /** Overrides the API host — a gateway, a proxy, or a regional endpoint. */
18
+ baseUrl?: string;
19
+ /** Reasoning-budget setting. Omitted, the API's own default (`high`) applies. */
20
+ effort?: AnthropicEffort;
21
+ /** Retries on 429/5xx/connection errors. Defaults to the SDK's 2. */
22
+ maxRetries?: number;
23
+ /** Per-request timeout in milliseconds. Defaults to the SDK's 10 minutes. */
24
+ timeoutMs?: number;
25
+ /**
26
+ * Whether to send `cache_control` breakpoints. **Defaults to on**, and
27
+ * should stay on: a run is a loop over a stateless API, so every step
28
+ * resends the whole conversation and an uncached run's billed input grows
29
+ * roughly with the square of its step count. Cache reads are ~0.1x base
30
+ * input price, and the write premium (1.25x) is repaid by the second step.
31
+ *
32
+ * The escape hatch exists for a {@link baseUrl} gateway or proxy that
33
+ * rejects the field rather than passing it through — the symptom is a 400
34
+ * on every run, not a quiet loss of caching.
35
+ */
36
+ promptCaching?: boolean;
37
+ }
38
+ /**
39
+ * Fallback capabilities, used when the Models API can't be reached (no
40
+ * network, no key, a gateway that doesn't proxy `/v1/models`). Conservative on
41
+ * the two numbers and honest on the three flags: every current Claude model
42
+ * streams, calls tools natively, and accepts images.
43
+ */
44
+ export declare const FALLBACK_CAPABILITIES: {
45
+ readonly toolCalling: true;
46
+ readonly streaming: true;
47
+ readonly vision: true;
48
+ readonly contextWindow: 200000;
49
+ readonly maxOutputTokens: 8192;
50
+ };
51
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/lib/config.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,MAAM,MAAM,eAAe,GAAG,KAAK,GAAG,QAAQ,GAAG,MAAM,GAAG,OAAO,GAAG,KAAK,CAAC;AAE1E,6CAA6C;AAC7C,MAAM,WAAW,uBAAuB;IACpC,uEAAuE;IACvE,MAAM,EAAE,MAAM,CAAC;IACf;;;;;OAKG;IACH,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC;IAC1B,2EAA2E;IAC3E,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,iFAAiF;IACjF,MAAM,CAAC,EAAE,eAAe,CAAC;IACzB,qEAAqE;IACrE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,6EAA6E;IAC7E,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;;;;;;;;OAUG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC;CAC3B;AAED;;;;;GAKG;AACH,eAAO,MAAM,qBAAqB;;;;;;CAMxB,CAAC"}
@@ -0,0 +1,16 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.FALLBACK_CAPABILITIES = void 0;
4
+ /**
5
+ * Fallback capabilities, used when the Models API can't be reached (no
6
+ * network, no key, a gateway that doesn't proxy `/v1/models`). Conservative on
7
+ * the two numbers and honest on the three flags: every current Claude model
8
+ * streams, calls tools natively, and accepts images.
9
+ */
10
+ exports.FALLBACK_CAPABILITIES = {
11
+ toolCalling: true,
12
+ streaming: true,
13
+ vision: true,
14
+ contextWindow: 200_000,
15
+ maxOutputTokens: 8_192
16
+ };
@@ -0,0 +1,42 @@
1
+ import type Anthropic from '@anthropic-ai/sdk';
2
+ import type { MessageParam, Tool } from '@anthropic-ai/sdk/resources/messages';
3
+ import type { ModelMessage, ModelRequest, ModelTool } from '@orthacms/copilot-domain';
4
+ import type { AnthropicProviderConfig } from '../config';
5
+ /**
6
+ * The body `messages.stream` accepts, derived from the method rather than
7
+ * imported by name — the SDK moves these aliases between barrels across
8
+ * releases, and the method signature is the thing we actually have to satisfy.
9
+ */
10
+ type StreamParams = Parameters<Anthropic['messages']['stream']>[0];
11
+ /** Maps the port's tool descriptors onto Anthropic's tool definitions. */
12
+ export declare function toAnthropicTools(tools: readonly ModelTool[]): Tool[];
13
+ /** Maps the port's block-structured turns onto Anthropic's message params. */
14
+ export declare function toAnthropicMessages(messages: readonly ModelMessage[]): MessageParam[];
15
+ /**
16
+ * Builds the streaming request body for an already-resolved `model`.
17
+ *
18
+ * **No `thinking` configuration is sent**, deliberately. On current models
19
+ * thinking is on by default, and turning it off makes them occasionally write
20
+ * a tool call into the *visible text* instead of emitting a tool-use block —
21
+ * the call then silently never runs, which for a tool-driven copilot is the
22
+ * worst available failure mode. The lever for cheaper runs is `config.effort`.
23
+ *
24
+ * No sampling parameters either: current models reject `temperature`, `top_p`
25
+ * and `top_k` outright, which is why the port never carried them.
26
+ *
27
+ * **Prompt caching is on unless the host turns it off.** A run is a loop, and
28
+ * the API is stateless, so step _n_ resends everything steps 1…_n_−1 already
29
+ * sent — the prompt grows every step while the *new* content is one tool
30
+ * result. Uncached, a run's billed input is roughly quadratic in its step
31
+ * count, which is what made the step ceiling behave like a cost ceiling. Cache
32
+ * reads are ~0.1x base input, so the resent prefix stops dominating and
33
+ * `maxTotalTokens` goes back to measuring new work.
34
+ *
35
+ * Placement follows the API's render order — `tools` → `system` → `messages` —
36
+ * so a single breakpoint at the end of `system` covers the tools with it. That
37
+ * one is the whole static prefix of every step in the run; the rest roll
38
+ * through the conversation ({@link withCacheBreakpoints}).
39
+ */
40
+ export declare function toStreamParams(request: ModelRequest, config: AnthropicProviderConfig, model: string): StreamParams;
41
+ export {};
42
+ //# sourceMappingURL=request.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"request.d.ts","sourceRoot":"","sources":["../../../src/lib/wire/request.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,SAAS,MAAM,mBAAmB,CAAC;AAC/C,OAAO,KAAK,EAER,YAAY,EACZ,IAAI,EACP,MAAM,sCAAsC,CAAC;AAC9C,OAAO,KAAK,EACR,YAAY,EACZ,YAAY,EACZ,SAAS,EACZ,MAAM,0BAA0B,CAAC;AAClC,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,WAAW,CAAC;AAEzD;;;;GAIG;AACH,KAAK,YAAY,GAAG,UAAU,CAAC,SAAS,CAAC,UAAU,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAwBnE,0EAA0E;AAC1E,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,SAAS,SAAS,EAAE,GAAG,IAAI,EAAE,CAMpE;AAED,8EAA8E;AAC9E,wBAAgB,mBAAmB,CAC/B,QAAQ,EAAE,SAAS,YAAY,EAAE,GAClC,YAAY,EAAE,CAwBhB;AA0DD;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,cAAc,CAC1B,OAAO,EAAE,YAAY,EACrB,MAAM,EAAE,uBAAuB,EAC/B,KAAK,EAAE,MAAM,GACd,YAAY,CA4Cd"}
@@ -0,0 +1,172 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.toAnthropicTools = toAnthropicTools;
4
+ exports.toAnthropicMessages = toAnthropicMessages;
5
+ exports.toStreamParams = toStreamParams;
6
+ /** A five-minute cache entry — the API's default TTL. See {@link markCache}. */
7
+ const EPHEMERAL = { type: 'ephemeral' };
8
+ /**
9
+ * The API's ceiling on `cache_control` breakpoints in one request. Exceeding it
10
+ * is a 400, so the budget is spent deliberately: one on the static prefix
11
+ * (tools + system), the rest rolling through the conversation.
12
+ */
13
+ const MAX_CACHE_BREAKPOINTS = 4;
14
+ /**
15
+ * How far back a breakpoint may sit from the previous one and still find it.
16
+ *
17
+ * A breakpoint walks back **at most 20 content blocks** looking for an existing
18
+ * entry; past that it misses *silently* — no error, just a full-price prefill
19
+ * and a `cache_read_input_tokens` of zero. A step that asks for one tool adds
20
+ * two or three blocks and never comes close, but a turn requesting tools in
21
+ * parallel adds a `tool_use` and a `tool_result` per call and can clear 20 on
22
+ * its own. 15 leaves margin for the blocks the next step will append.
23
+ */
24
+ const CACHE_LOOKBACK_MARGIN = 15;
25
+ /** Maps the port's tool descriptors onto Anthropic's tool definitions. */
26
+ function toAnthropicTools(tools) {
27
+ return tools.map((tool) => ({
28
+ name: tool.name,
29
+ description: tool.description,
30
+ input_schema: tool.inputSchema
31
+ }));
32
+ }
33
+ /** Maps the port's block-structured turns onto Anthropic's message params. */
34
+ function toAnthropicMessages(messages) {
35
+ return messages.map((message) => ({
36
+ role: message.role,
37
+ content: message.content.map((block) => {
38
+ switch (block.type) {
39
+ case 'text':
40
+ return { type: 'text', text: block.text };
41
+ case 'tool_use':
42
+ return {
43
+ type: 'tool_use',
44
+ id: block.id,
45
+ name: block.name,
46
+ input: block.input
47
+ };
48
+ case 'tool_result':
49
+ return {
50
+ type: 'tool_result',
51
+ tool_use_id: block.toolUseId,
52
+ content: block.content,
53
+ ...(block.isError ? { is_error: true } : {})
54
+ };
55
+ }
56
+ })
57
+ }));
58
+ }
59
+ /** Returns `block` with a cache breakpoint on it. Never mutates the input. */
60
+ function markCache(block) {
61
+ return { ...block, cache_control: EPHEMERAL };
62
+ }
63
+ /**
64
+ * Places rolling cache breakpoints through the conversation, newest first.
65
+ *
66
+ * The newest turn always gets one, so the *next* step reads everything this
67
+ * step sent rather than re-prefilling it. Older turns get one only when enough
68
+ * blocks have accumulated to threaten {@link CACHE_LOOKBACK_MARGIN} — an extra
69
+ * breakpoint is a cache *write*, so they are placed to keep the chain
70
+ * reachable, not on every turn.
71
+ *
72
+ * Returns a new array; `messages` is left alone.
73
+ */
74
+ function withCacheBreakpoints(messages, budget) {
75
+ const marked = new Set();
76
+ let sinceLast = 0;
77
+ for (let i = messages.length - 1; i >= 0 && marked.size < budget; i -= 1) {
78
+ const content = messages[i].content;
79
+ // Always an array out of `toAnthropicMessages`, but the SDK's own type
80
+ // allows a bare string, and there is no block on a string to mark.
81
+ if (!Array.isArray(content) || content.length === 0) {
82
+ continue;
83
+ }
84
+ const isNewest = marked.size === 0;
85
+ if (isNewest || sinceLast + content.length >= CACHE_LOOKBACK_MARGIN) {
86
+ marked.add(i);
87
+ // The blocks of this message that sit *before* the one just marked
88
+ // still count toward the next breakpoint's walk back.
89
+ sinceLast = content.length - 1;
90
+ }
91
+ else {
92
+ sinceLast += content.length;
93
+ }
94
+ }
95
+ if (marked.size === 0) {
96
+ return messages;
97
+ }
98
+ return messages.map((message, index) => {
99
+ if (!marked.has(index) || !Array.isArray(message.content)) {
100
+ return message;
101
+ }
102
+ const content = [...message.content];
103
+ content[content.length - 1] = markCache(content[content.length - 1]);
104
+ return { ...message, content };
105
+ });
106
+ }
107
+ /**
108
+ * Builds the streaming request body for an already-resolved `model`.
109
+ *
110
+ * **No `thinking` configuration is sent**, deliberately. On current models
111
+ * thinking is on by default, and turning it off makes them occasionally write
112
+ * a tool call into the *visible text* instead of emitting a tool-use block —
113
+ * the call then silently never runs, which for a tool-driven copilot is the
114
+ * worst available failure mode. The lever for cheaper runs is `config.effort`.
115
+ *
116
+ * No sampling parameters either: current models reject `temperature`, `top_p`
117
+ * and `top_k` outright, which is why the port never carried them.
118
+ *
119
+ * **Prompt caching is on unless the host turns it off.** A run is a loop, and
120
+ * the API is stateless, so step _n_ resends everything steps 1…_n_−1 already
121
+ * sent — the prompt grows every step while the *new* content is one tool
122
+ * result. Uncached, a run's billed input is roughly quadratic in its step
123
+ * count, which is what made the step ceiling behave like a cost ceiling. Cache
124
+ * reads are ~0.1x base input, so the resent prefix stops dominating and
125
+ * `maxTotalTokens` goes back to measuring new work.
126
+ *
127
+ * Placement follows the API's render order — `tools` → `system` → `messages` —
128
+ * so a single breakpoint at the end of `system` covers the tools with it. That
129
+ * one is the whole static prefix of every step in the run; the rest roll
130
+ * through the conversation ({@link withCacheBreakpoints}).
131
+ */
132
+ function toStreamParams(request, config, model) {
133
+ const tools = request.tools ?? [];
134
+ const anthropicTools = toAnthropicTools(tools);
135
+ const messages = toAnthropicMessages(request.messages);
136
+ // Off only by explicit opt-out: a `baseUrl` gateway that rejects the field
137
+ // is the case this exists for, and it is rarer than the loop it pays for.
138
+ const caching = config.promptCaching !== false;
139
+ // The breakpoint that matters most, on whichever block ends the static
140
+ // prefix. With a system prompt that is its last block — tools render ahead
141
+ // of it and are covered by the same entry. Without one, the last tool is
142
+ // the end of the prefix, and marking it is the only way to cache the tools
143
+ // at all. (Below the model's minimum cacheable prefix nothing is written;
144
+ // that is silent by design and costs nothing.)
145
+ const cacheSystem = caching && Boolean(request.system);
146
+ const cacheTools = caching && !cacheSystem && anthropicTools.length > 0;
147
+ if (cacheTools) {
148
+ anthropicTools[anthropicTools.length - 1] = markCache(anthropicTools[anthropicTools.length - 1]);
149
+ }
150
+ return {
151
+ model,
152
+ max_tokens: request.maxOutputTokens,
153
+ ...(request.system
154
+ ? {
155
+ system: cacheSystem
156
+ ? [
157
+ {
158
+ type: 'text',
159
+ text: request.system,
160
+ cache_control: EPHEMERAL
161
+ }
162
+ ]
163
+ : request.system
164
+ }
165
+ : {}),
166
+ messages: caching
167
+ ? withCacheBreakpoints(messages, MAX_CACHE_BREAKPOINTS - 1)
168
+ : messages,
169
+ ...(anthropicTools.length > 0 ? { tools: anthropicTools } : {}),
170
+ ...(config.effort ? { output_config: { effort: config.effort } } : {})
171
+ };
172
+ }
@@ -0,0 +1,14 @@
1
+ import type { StopReason, Usage } from '@anthropic-ai/sdk/resources/messages';
2
+ import type { ModelStopReason, ModelUsage } from '@orthacms/copilot-domain';
3
+ /** Maps Anthropic's stop reasons onto the port's smaller vocabulary. */
4
+ export declare function toStopReason(stopReason: StopReason | null): ModelStopReason;
5
+ /**
6
+ * Maps Anthropic's usage record onto the port's, dropping absent cache stats.
7
+ *
8
+ * The three input counts are **disjoint**: `input_tokens` is the uncached
9
+ * remainder only, and the whole prompt is `input_tokens` + the two cache
10
+ * figures. Reading only the first is how an agent that ran for an hour reports
11
+ * 4k of input.
12
+ */
13
+ export declare function toUsage(usage: Usage): ModelUsage;
14
+ //# sourceMappingURL=response.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"response.d.ts","sourceRoot":"","sources":["../../../src/lib/wire/response.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,sCAAsC,CAAC;AAC9E,OAAO,KAAK,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAC;AAE5E,wEAAwE;AACxE,wBAAgB,YAAY,CAAC,UAAU,EAAE,UAAU,GAAG,IAAI,GAAG,eAAe,CAe3E;AAED;;;;;;;GAOG;AACH,wBAAgB,OAAO,CAAC,KAAK,EAAE,KAAK,GAAG,UAAU,CAahD"}
@@ -0,0 +1,43 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.toStopReason = toStopReason;
4
+ exports.toUsage = toUsage;
5
+ /** Maps Anthropic's stop reasons onto the port's smaller vocabulary. */
6
+ function toStopReason(stopReason) {
7
+ switch (stopReason) {
8
+ case 'tool_use':
9
+ return 'tool_use';
10
+ case 'refusal':
11
+ return 'refusal';
12
+ case 'max_tokens':
13
+ case 'model_context_window_exceeded':
14
+ // Both mean "ran out of room" — the answer is truncated either way.
15
+ return 'max_tokens';
16
+ default:
17
+ // `end_turn`, `stop_sequence`, `pause_turn` (unreachable without
18
+ // server-side tools, which this adapter never enables), and null.
19
+ return 'end';
20
+ }
21
+ }
22
+ /**
23
+ * Maps Anthropic's usage record onto the port's, dropping absent cache stats.
24
+ *
25
+ * The three input counts are **disjoint**: `input_tokens` is the uncached
26
+ * remainder only, and the whole prompt is `input_tokens` + the two cache
27
+ * figures. Reading only the first is how an agent that ran for an hour reports
28
+ * 4k of input.
29
+ */
30
+ function toUsage(usage) {
31
+ const cached = usage.cache_read_input_tokens;
32
+ const written = usage.cache_creation_input_tokens;
33
+ return {
34
+ inputTokens: usage.input_tokens,
35
+ outputTokens: usage.output_tokens,
36
+ ...(cached === null || cached === undefined
37
+ ? {}
38
+ : { cachedInputTokens: cached }),
39
+ ...(written === null || written === undefined
40
+ ? {}
41
+ : { cacheWriteInputTokens: written })
42
+ };
43
+ }
package/package.json ADDED
@@ -0,0 +1,35 @@
1
+ {
2
+ "name": "@orthacms/copilot-provider-anthropic",
3
+ "version": "0.0.0-reserve.0",
4
+ "description": "@orthacms/copilot-provider-anthropic — part of Ortha CMS.",
5
+ "license": "MIT",
6
+ "homepage": "https://github.com/ortha-source/ortha-cms/tree/main/packages/copilot/provider-anthropic",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/ortha-source/ortha-cms.git",
10
+ "directory": "packages/copilot/provider-anthropic"
11
+ },
12
+ "bugs": {
13
+ "url": "https://github.com/ortha-source/ortha-cms/issues"
14
+ },
15
+ "main": "./dist/index.js",
16
+ "types": "./dist/index.d.ts",
17
+ "exports": {
18
+ ".": {
19
+ "types": "./dist/index.d.ts",
20
+ "default": "./dist/index.js"
21
+ },
22
+ "./package.json": "./package.json"
23
+ },
24
+ "files": [
25
+ "dist"
26
+ ],
27
+ "dependencies": {
28
+ "@anthropic-ai/sdk": "^0.115.0",
29
+ "@orthacms/copilot-domain": "^0.0.1",
30
+ "tslib": "^2.3.0"
31
+ },
32
+ "publishConfig": {
33
+ "access": "public"
34
+ }
35
+ }