@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 +21 -0
- package/README.md +7 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +5 -0
- package/dist/lib/anthropic-provider.d.ts +23 -0
- package/dist/lib/anthropic-provider.d.ts.map +1 -0
- package/dist/lib/anthropic-provider.js +101 -0
- package/dist/lib/capabilities.d.ts +26 -0
- package/dist/lib/capabilities.d.ts.map +1 -0
- package/dist/lib/capabilities.js +41 -0
- package/dist/lib/client.d.ts +15 -0
- package/dist/lib/client.d.ts.map +1 -0
- package/dist/lib/client.js +36 -0
- package/dist/lib/config.d.ts +51 -0
- package/dist/lib/config.d.ts.map +1 -0
- package/dist/lib/config.js +16 -0
- package/dist/lib/wire/request.d.ts +42 -0
- package/dist/lib/wire/request.d.ts.map +1 -0
- package/dist/lib/wire/request.js +172 -0
- package/dist/lib/wire/response.d.ts +14 -0
- package/dist/lib/wire/response.d.ts.map +1 -0
- package/dist/lib/wire/response.js +43 -0
- package/package.json +35 -0
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
package/dist/index.d.ts
ADDED
|
@@ -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
|
+
}
|