defiant-jazz 0.6.2 → 0.8.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/README.md +67 -4
- package/dist/client.js +21 -8
- package/dist/defaults.d.ts +7 -0
- package/dist/defaults.js +9 -0
- package/dist/index.d.ts +4 -3
- package/dist/index.js +2 -1
- package/dist/providers/anthropic.d.ts +3 -2
- package/dist/providers/anthropic.js +12 -2
- package/dist/providers/openai.d.ts +2 -2
- package/dist/providers/openai.js +10 -2
- package/dist/types.d.ts +28 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -28,15 +28,21 @@ await refine("Hello world", "dylan");
|
|
|
28
28
|
```
|
|
29
29
|
|
|
30
30
|
Set `ANTHROPIC_API_KEY` or `OPENAI_API_KEY` in your environment and you're done —
|
|
31
|
-
whichever is present is used
|
|
31
|
+
whichever one is present is used. If both are set, the library won't pick for
|
|
32
|
+
you; pass `provider` instead (see [Choosing a provider](#choosing-a-provider)).
|
|
32
33
|
|
|
33
34
|
**Default models: `claude-sonnet-5` (Anthropic), `gpt-4o-mini` (OpenAI).**
|
|
34
35
|
Sonnet 5 was chosen over Opus 5 after a head-to-head comparison found
|
|
35
36
|
matching output quality at 40% of the cost — see
|
|
36
37
|
[model-comparison.md](model-comparison.md) for the full writeup, including a
|
|
37
38
|
documented regression on Haiku 4.5 that ruled it out as a default. Override
|
|
38
|
-
with `model` on `createDefiantJazz()` or per call
|
|
39
|
-
|
|
39
|
+
with `model` on `createDefiantJazz()` or per call. The defaults are also
|
|
40
|
+
exported, so you can check what a zero-config call will run:
|
|
41
|
+
|
|
42
|
+
```typescript
|
|
43
|
+
import { DEFAULT_MODELS } from 'defiant-jazz';
|
|
44
|
+
// { anthropic: 'claude-sonnet-5', openai: 'gpt-4o-mini' }
|
|
45
|
+
```
|
|
40
46
|
|
|
41
47
|
## Lore
|
|
42
48
|
|
|
@@ -91,7 +97,28 @@ await claude.dylan("Hello world");
|
|
|
91
97
|
await gpt.dylan("Hello world");
|
|
92
98
|
```
|
|
93
99
|
|
|
94
|
-
|
|
100
|
+
How the provider is chosen:
|
|
101
|
+
|
|
102
|
+
- `provider` set → that provider. Its key comes from `apiKey`, or from
|
|
103
|
+
`ANTHROPIC_API_KEY` / `OPENAI_API_KEY` if `apiKey` is omitted.
|
|
104
|
+
- `provider` omitted, exactly one env key set → that provider.
|
|
105
|
+
- `provider` omitted, both env keys set → throws. The library never decides
|
|
106
|
+
whose account gets billed.
|
|
107
|
+
- `apiKey` passed without `provider` → throws, so a key is never sent to the
|
|
108
|
+
wrong service.
|
|
109
|
+
|
|
110
|
+
Only those two env var names are read. To use a key stored under a different
|
|
111
|
+
name, read it yourself and pass it as `apiKey`:
|
|
112
|
+
|
|
113
|
+
```typescript
|
|
114
|
+
const gpt = createDefiantJazz({
|
|
115
|
+
provider: 'openai',
|
|
116
|
+
apiKey: process.env.MY_TEAM_OPENAI_KEY,
|
|
117
|
+
});
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
`refine` is a shared default instance that follows the same rules; point it at
|
|
121
|
+
a provider with `refine.configure({ provider: 'openai' })`. Use
|
|
95
122
|
`createDefiantJazz()` whenever you need an isolated instance — a specific
|
|
96
123
|
provider, different API keys per request, a non-default model, testing.
|
|
97
124
|
|
|
@@ -101,6 +128,42 @@ Per-call overrides work on both forms:
|
|
|
101
128
|
await refine.dylan("Hello world", { model: 'claude-opus-5', maxTokens: 1024 });
|
|
102
129
|
```
|
|
103
130
|
|
|
131
|
+
### Effort (Anthropic only)
|
|
132
|
+
|
|
133
|
+
`effort` controls how much the model reasons before answering: `'low'`,
|
|
134
|
+
`'medium'`, `'high'`, `'xhigh'` or `'max'`. Lower is cheaper and faster. Leave
|
|
135
|
+
it unset to use the model's own default, which varies by model.
|
|
136
|
+
|
|
137
|
+
```typescript
|
|
138
|
+
const fast = createDefiantJazz({ effort: 'low' });
|
|
139
|
+
await refine.dylan("Hello world", { effort: 'low' }); // per-call works too
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
- Setting `effort` with the OpenAI provider throws.
|
|
143
|
+
- Not every model accepts every level (older models may reject `effort`
|
|
144
|
+
entirely); the API's error is passed through.
|
|
145
|
+
- Reasoning tokens count toward `maxTokens`. At `xhigh` or `max`, raise
|
|
146
|
+
`maxTokens` if you see "returned no text content (stop_reason: max_tokens)".
|
|
147
|
+
|
|
148
|
+
## Usage and cost
|
|
149
|
+
|
|
150
|
+
Every result includes the model that answered and the request's token
|
|
151
|
+
counts, normalized across providers:
|
|
152
|
+
|
|
153
|
+
```typescript
|
|
154
|
+
const result = await refine.dylan("Hello world");
|
|
155
|
+
// {
|
|
156
|
+
// text: "...",
|
|
157
|
+
// character: "Dylan G.",
|
|
158
|
+
// model: "claude-sonnet-5",
|
|
159
|
+
// usage: { inputTokens: 2770, outputTokens: 77 },
|
|
160
|
+
// }
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
`outputTokens` includes any reasoning tokens the model spent before answering
|
|
164
|
+
(see [Effort](#effort-anthropic-only)). `usage` is omitted if the provider
|
|
165
|
+
doesn't report it.
|
|
166
|
+
|
|
104
167
|
## Characters
|
|
105
168
|
|
|
106
169
|
```typescript
|
package/dist/client.js
CHANGED
|
@@ -12,14 +12,23 @@ function resolveProvider(options) {
|
|
|
12
12
|
}
|
|
13
13
|
return { provider: options.provider, apiKey };
|
|
14
14
|
}
|
|
15
|
+
// No explicit provider. Only infer one when there's exactly one candidate;
|
|
16
|
+
// never guess between two, since that picks whose account gets billed.
|
|
15
17
|
if (options.apiKey) {
|
|
16
|
-
|
|
18
|
+
throw new Error("apiKey requires provider so the key isn't sent to the wrong service. " +
|
|
19
|
+
"Pass { provider: 'anthropic' } or { provider: 'openai' } alongside it.");
|
|
17
20
|
}
|
|
18
|
-
|
|
19
|
-
|
|
21
|
+
const anthropicKey = process.env.ANTHROPIC_API_KEY;
|
|
22
|
+
const openaiKey = process.env.OPENAI_API_KEY;
|
|
23
|
+
if (anthropicKey && openaiKey) {
|
|
24
|
+
throw new Error("Both ANTHROPIC_API_KEY and OPENAI_API_KEY are set. " +
|
|
25
|
+
"Pass { provider: 'anthropic' } or { provider: 'openai' } to choose.");
|
|
20
26
|
}
|
|
21
|
-
if (
|
|
22
|
-
return { provider: "
|
|
27
|
+
if (anthropicKey) {
|
|
28
|
+
return { provider: "anthropic", apiKey: anthropicKey };
|
|
29
|
+
}
|
|
30
|
+
if (openaiKey) {
|
|
31
|
+
return { provider: "openai", apiKey: openaiKey };
|
|
23
32
|
}
|
|
24
33
|
throw new Error("No API key found. Set ANTHROPIC_API_KEY or OPENAI_API_KEY " +
|
|
25
34
|
"(or pass apiKey/provider to configure()/createDefiantJazz()).");
|
|
@@ -51,13 +60,17 @@ export function createDefiantJazz(initialOptions = {}) {
|
|
|
51
60
|
}
|
|
52
61
|
const characterName = CHARACTERS[characterKey];
|
|
53
62
|
const { provider, client } = getClient();
|
|
54
|
-
const
|
|
63
|
+
const effort = options?.effort ?? config.effort;
|
|
64
|
+
if (effort !== undefined && provider === "openai") {
|
|
65
|
+
throw new Error("effort is only supported with the Anthropic provider.");
|
|
66
|
+
}
|
|
67
|
+
const defaults = { model: config.model, maxTokens: config.maxTokens, effort: config.effort };
|
|
55
68
|
const lore = options?.lore ?? config.lore ?? true;
|
|
56
69
|
const systemPrompt = buildSystemPrompt(lore);
|
|
57
|
-
const
|
|
70
|
+
const result = provider === "anthropic"
|
|
58
71
|
? await runAnthropic(client, characterName, text, systemPrompt, defaults, options)
|
|
59
72
|
: await runOpenAi(client, characterName, text, systemPrompt, defaults, options);
|
|
60
|
-
return {
|
|
73
|
+
return { ...result, character: characterName };
|
|
61
74
|
}
|
|
62
75
|
const fn = ((text, character, options) => transform(text, character, options));
|
|
63
76
|
fn.mark = (text, options) => transform(text, "mark", options);
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { Provider } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* The model each provider uses when `model` is not set. Exported so callers
|
|
4
|
+
* can see what a zero-config call will run (and be billed for) without
|
|
5
|
+
* reading the docs. See model-comparison.md for why Sonnet 5 is the default.
|
|
6
|
+
*/
|
|
7
|
+
export declare const DEFAULT_MODELS: Readonly<Record<Provider, string>>;
|
package/dist/defaults.js
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The model each provider uses when `model` is not set. Exported so callers
|
|
3
|
+
* can see what a zero-config call will run (and be billed for) without
|
|
4
|
+
* reading the docs. See model-comparison.md for why Sonnet 5 is the default.
|
|
5
|
+
*/
|
|
6
|
+
export const DEFAULT_MODELS = Object.freeze({
|
|
7
|
+
anthropic: "claude-sonnet-5",
|
|
8
|
+
openai: "gpt-4o-mini",
|
|
9
|
+
});
|
package/dist/index.d.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { createDefiantJazz, type DefiantJazz } from "./client.js";
|
|
2
|
+
import { DEFAULT_MODELS } from "./defaults.js";
|
|
2
3
|
import { CHARACTERS } from "./prompt.js";
|
|
3
|
-
import type { CharacterKey, DefiantJazzOptions, RefineOptions, RefineResult } from "./types.js";
|
|
4
|
-
export type { CharacterKey, DefiantJazzOptions, RefineOptions, RefineResult };
|
|
5
|
-
export { CHARACTERS, createDefiantJazz };
|
|
4
|
+
import type { CharacterKey, DefiantJazzOptions, Effort, RefineOptions, RefineResult, TokenUsage } from "./types.js";
|
|
5
|
+
export type { CharacterKey, DefiantJazzOptions, Effort, RefineOptions, RefineResult, TokenUsage, };
|
|
6
|
+
export { CHARACTERS, DEFAULT_MODELS, createDefiantJazz };
|
|
6
7
|
/**
|
|
7
8
|
* Configure the default `refine` instance with an API key and optional
|
|
8
9
|
* model/provider overrides. Prefer `createDefiantJazz()` for an isolated
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { createDefiantJazz } from "./client.js";
|
|
2
|
+
import { DEFAULT_MODELS } from "./defaults.js";
|
|
2
3
|
import { CHARACTERS } from "./prompt.js";
|
|
3
|
-
export { CHARACTERS, createDefiantJazz };
|
|
4
|
+
export { CHARACTERS, DEFAULT_MODELS, createDefiantJazz };
|
|
4
5
|
let defaultInstance = null;
|
|
5
6
|
function getDefaultInstance() {
|
|
6
7
|
if (!defaultInstance) {
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import Anthropic from "@anthropic-ai/sdk";
|
|
2
|
-
import type { RefineOptions } from "../types.js";
|
|
2
|
+
import type { Effort, RefineOptions, RefineResult } from "../types.js";
|
|
3
3
|
export declare function createAnthropicClient(apiKey: string): Anthropic;
|
|
4
4
|
export declare function runAnthropic(client: Anthropic, characterName: string, text: string, systemPrompt: string, defaults: {
|
|
5
5
|
model?: string;
|
|
6
6
|
maxTokens?: number;
|
|
7
|
-
|
|
7
|
+
effort?: Effort;
|
|
8
|
+
}, options?: RefineOptions): Promise<Omit<RefineResult, "character">>;
|
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
import Anthropic from "@anthropic-ai/sdk";
|
|
2
|
-
|
|
2
|
+
import { DEFAULT_MODELS } from "../defaults.js";
|
|
3
|
+
const DEFAULT_MODEL = DEFAULT_MODELS.anthropic;
|
|
3
4
|
const DEFAULT_MAX_TOKENS = 4096;
|
|
4
5
|
export function createAnthropicClient(apiKey) {
|
|
5
6
|
return new Anthropic({ apiKey });
|
|
6
7
|
}
|
|
7
8
|
export async function runAnthropic(client, characterName, text, systemPrompt, defaults, options) {
|
|
9
|
+
const effort = options?.effort ?? defaults.effort;
|
|
8
10
|
try {
|
|
9
11
|
const response = await client.messages.create({
|
|
10
12
|
model: options?.model ?? defaults.model ?? DEFAULT_MODEL,
|
|
11
13
|
max_tokens: options?.maxTokens ?? defaults.maxTokens ?? DEFAULT_MAX_TOKENS,
|
|
14
|
+
...(effort !== undefined && { output_config: { effort } }),
|
|
12
15
|
system: systemPrompt,
|
|
13
16
|
messages: [
|
|
14
17
|
{
|
|
@@ -21,7 +24,14 @@ export async function runAnthropic(client, characterName, text, systemPrompt, de
|
|
|
21
24
|
if (!textBlock) {
|
|
22
25
|
throw new Error(`Anthropic returned no text content (stop_reason: ${response.stop_reason})`);
|
|
23
26
|
}
|
|
24
|
-
return
|
|
27
|
+
return {
|
|
28
|
+
text: textBlock.text,
|
|
29
|
+
model: response.model,
|
|
30
|
+
usage: response.usage && {
|
|
31
|
+
inputTokens: response.usage.input_tokens,
|
|
32
|
+
outputTokens: response.usage.output_tokens,
|
|
33
|
+
},
|
|
34
|
+
};
|
|
25
35
|
}
|
|
26
36
|
catch (error) {
|
|
27
37
|
if (error instanceof Anthropic.AuthenticationError) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import OpenAI from "openai";
|
|
2
|
-
import type { RefineOptions } from "../types.js";
|
|
2
|
+
import type { RefineOptions, RefineResult } from "../types.js";
|
|
3
3
|
export declare function createOpenAiClient(apiKey: string): OpenAI;
|
|
4
4
|
export declare function runOpenAi(client: OpenAI, characterName: string, text: string, systemPrompt: string, defaults: {
|
|
5
5
|
model?: string;
|
|
6
6
|
maxTokens?: number;
|
|
7
|
-
}, options?: RefineOptions): Promise<
|
|
7
|
+
}, options?: RefineOptions): Promise<Omit<RefineResult, "character">>;
|
package/dist/providers/openai.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import OpenAI from "openai";
|
|
2
|
-
|
|
2
|
+
import { DEFAULT_MODELS } from "../defaults.js";
|
|
3
|
+
const DEFAULT_MODEL = DEFAULT_MODELS.openai;
|
|
3
4
|
const DEFAULT_MAX_TOKENS = 4096;
|
|
4
5
|
export function createOpenAiClient(apiKey) {
|
|
5
6
|
return new OpenAI({ apiKey });
|
|
@@ -20,5 +21,12 @@ export async function runOpenAi(client, characterName, text, systemPrompt, defau
|
|
|
20
21
|
if (!content) {
|
|
21
22
|
throw new Error("OpenAI returned no content.");
|
|
22
23
|
}
|
|
23
|
-
return
|
|
24
|
+
return {
|
|
25
|
+
text: content,
|
|
26
|
+
model: completion.model,
|
|
27
|
+
usage: completion.usage && {
|
|
28
|
+
inputTokens: completion.usage.prompt_tokens,
|
|
29
|
+
outputTokens: completion.usage.completion_tokens,
|
|
30
|
+
},
|
|
31
|
+
};
|
|
24
32
|
}
|
package/dist/types.d.ts
CHANGED
|
@@ -1,15 +1,37 @@
|
|
|
1
|
+
import type Anthropic from "@anthropic-ai/sdk";
|
|
1
2
|
export type CharacterKey = "mark" | "irving" | "dylan" | "milchick";
|
|
2
3
|
export type Provider = "anthropic" | "openai";
|
|
4
|
+
/** Anthropic effort level, taken from the SDK so new levels arrive with SDK updates. */
|
|
5
|
+
export type Effort = NonNullable<Anthropic.OutputConfig["effort"]>;
|
|
6
|
+
export interface TokenUsage {
|
|
7
|
+
inputTokens: number;
|
|
8
|
+
/** Includes any reasoning tokens the model spent before answering. */
|
|
9
|
+
outputTokens: number;
|
|
10
|
+
}
|
|
3
11
|
export interface RefineResult {
|
|
4
12
|
text: string;
|
|
5
13
|
character: string;
|
|
14
|
+
/** The model that answered, as reported by the provider. */
|
|
15
|
+
model: string;
|
|
16
|
+
/** Token counts for this request, when the provider reports them. */
|
|
17
|
+
usage?: TokenUsage;
|
|
6
18
|
}
|
|
7
19
|
export interface DefiantJazzOptions {
|
|
8
|
-
/**
|
|
20
|
+
/**
|
|
21
|
+
* Which API to call. Optional only when exactly one of `ANTHROPIC_API_KEY`
|
|
22
|
+
* or `OPENAI_API_KEY` is set and `apiKey` is not passed; when the choice
|
|
23
|
+
* would be ambiguous, the library throws rather than guess.
|
|
24
|
+
*/
|
|
9
25
|
provider?: Provider;
|
|
10
26
|
apiKey?: string;
|
|
11
27
|
model?: string;
|
|
12
28
|
maxTokens?: number;
|
|
29
|
+
/**
|
|
30
|
+
* How much the model reasons before answering (Anthropic only). Lower is
|
|
31
|
+
* cheaper and faster. Leave unset to use the model's own default, which
|
|
32
|
+
* varies by model. Setting it with the OpenAI provider throws.
|
|
33
|
+
*/
|
|
34
|
+
effort?: Effort;
|
|
13
35
|
/**
|
|
14
36
|
* Whether characters reach for Lumon/Severance vocabulary (the break room,
|
|
15
37
|
* waffle parties, Kier, innie/outie, etc.) when a quote's situation calls
|
|
@@ -21,6 +43,11 @@ export interface DefiantJazzOptions {
|
|
|
21
43
|
export interface RefineOptions {
|
|
22
44
|
model?: string;
|
|
23
45
|
maxTokens?: number;
|
|
46
|
+
/**
|
|
47
|
+
* How much the model reasons before answering (Anthropic only). Overrides
|
|
48
|
+
* the instance-level setting. Leave unset to use the model's own default.
|
|
49
|
+
*/
|
|
50
|
+
effort?: Effort;
|
|
24
51
|
signal?: AbortSignal;
|
|
25
52
|
/**
|
|
26
53
|
* Whether this call reaches for Lumon/Severance vocabulary. Overrides the
|