@providerkit/core 0.17.2 → 0.20.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 +1 -0
- package/dist/auth/device.d.ts +34 -0
- package/dist/auth/device.d.ts.map +1 -0
- package/dist/auth/device.js +85 -0
- package/dist/auth/device.js.map +1 -0
- package/dist/auth/flows.d.ts +36 -0
- package/dist/auth/flows.d.ts.map +1 -0
- package/dist/auth/flows.js +392 -0
- package/dist/auth/flows.js.map +1 -0
- package/dist/auth/index.d.ts +5 -0
- package/dist/auth/index.d.ts.map +1 -0
- package/dist/auth/index.js +8 -0
- package/dist/auth/index.js.map +1 -0
- package/dist/auth/oauth.d.ts +92 -0
- package/dist/auth/oauth.d.ts.map +1 -0
- package/dist/auth/oauth.js +148 -0
- package/dist/auth/oauth.js.map +1 -0
- package/dist/auth/token-source.d.ts +30 -0
- package/dist/auth/token-source.d.ts.map +1 -0
- package/dist/auth/token-source.js +61 -0
- package/dist/auth/token-source.js.map +1 -0
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +17 -1
- package/dist/context.js.map +1 -1
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +3 -0
- package/dist/errors.js.map +1 -1
- package/dist/presets.d.ts +32 -3
- package/dist/presets.d.ts.map +1 -1
- package/dist/presets.js +24 -2
- package/dist/presets.js.map +1 -1
- package/dist/providers/anthropic.d.ts.map +1 -1
- package/dist/providers/anthropic.js +6 -1
- package/dist/providers/anthropic.js.map +1 -1
- package/dist/providers/factory.d.ts +8 -2
- package/dist/providers/factory.d.ts.map +1 -1
- package/dist/providers/factory.js +11 -4
- package/dist/providers/factory.js.map +1 -1
- package/dist/providers/gemini.d.ts.map +1 -1
- package/dist/providers/gemini.js +3 -0
- package/dist/providers/gemini.js.map +1 -1
- package/dist/providers/openai.d.ts +7 -1
- package/dist/providers/openai.d.ts.map +1 -1
- package/dist/providers/openai.js +6 -0
- package/dist/providers/openai.js.map +1 -1
- package/dist/providers/responses.d.ts +7 -1
- package/dist/providers/responses.d.ts.map +1 -1
- package/dist/providers/responses.js +48 -6
- package/dist/providers/responses.js.map +1 -1
- package/dist/types.d.ts +26 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +13 -5
- package/dist/types.js.map +1 -1
- package/package.json +5 -1
- package/src/auth/device.ts +137 -0
- package/src/auth/flows.ts +526 -0
- package/src/auth/index.ts +7 -0
- package/src/auth/oauth.ts +226 -0
- package/src/auth/token-source.ts +76 -0
- package/src/context.ts +16 -1
- package/src/errors.ts +3 -0
- package/src/presets.ts +33 -4
- package/src/providers/anthropic.ts +10 -1
- package/src/providers/factory.ts +21 -6
- package/src/providers/gemini.ts +7 -0
- package/src/providers/openai.ts +12 -0
- package/src/providers/responses.ts +60 -6
- package/src/types.ts +49 -5
|
@@ -20,6 +20,7 @@ import type {
|
|
|
20
20
|
ImagePart,
|
|
21
21
|
Provider,
|
|
22
22
|
ProviderChunk,
|
|
23
|
+
ServiceTier,
|
|
23
24
|
StreamOptions,
|
|
24
25
|
ToolDefinition,
|
|
25
26
|
} from "../types.ts";
|
|
@@ -56,6 +57,12 @@ export interface ResponsesConfig extends ProviderFallbackConfig {
|
|
|
56
57
|
/** What this backend accepts as an image. Each image it would refuse is
|
|
57
58
|
* replaced by a note naming the reason, instead of failing the request. */
|
|
58
59
|
imageLimits?: ImageLimits;
|
|
60
|
+
/** Ask for the encrypted reasoning items and replay them on the next turn,
|
|
61
|
+
* so the model keeps its chain of thought across a stateless turn. Only for
|
|
62
|
+
* backends that accept `include: ["reasoning.encrypted_content"]`. */
|
|
63
|
+
replayReasoning?: boolean;
|
|
64
|
+
/** Default `service_tier` for every call; `StreamOptions.serviceTier` wins. */
|
|
65
|
+
serviceTier?: ServiceTier;
|
|
59
66
|
}
|
|
60
67
|
|
|
61
68
|
const DEFAULT_BASE_URL = "https://api.openai.com";
|
|
@@ -91,7 +98,33 @@ type ResponsesContentPart =
|
|
|
91
98
|
| { type: "output_text"; text: string }
|
|
92
99
|
| { type: "input_image"; image_url: string };
|
|
93
100
|
|
|
101
|
+
/** A reasoning item the backend made, sent back as it came. */
|
|
102
|
+
type ReplayedReasoning = { type: "reasoning" } & Record<string, unknown>;
|
|
103
|
+
|
|
104
|
+
function isReplayedReasoning(item: unknown): item is ReplayedReasoning {
|
|
105
|
+
return typeof item === "object" && item !== null && "type" in item && item.type === "reasoning";
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/** The most encrypted reasoning one request carries back. Newest turns win. */
|
|
109
|
+
const MAX_REPLAY_BYTES = 8 * 1024 * 1024;
|
|
110
|
+
|
|
111
|
+
/** Which assistant messages get their reasoning replayed: this provider's own,
|
|
112
|
+
* newest first, until the cap. An older turn's reasoning is the cheapest to lose. */
|
|
113
|
+
function replayable(messages: readonly ChatMessage[], provider: string): Set<ChatMessage> {
|
|
114
|
+
const keep = new Set<ChatMessage>();
|
|
115
|
+
let bytes = 0;
|
|
116
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
117
|
+
const message = messages[i]!;
|
|
118
|
+
if (message.role !== "assistant" || message.reasoningItems?.provider !== provider) continue;
|
|
119
|
+
bytes += JSON.stringify(message.reasoningItems.items).length;
|
|
120
|
+
if (bytes > MAX_REPLAY_BYTES) break;
|
|
121
|
+
keep.add(message);
|
|
122
|
+
}
|
|
123
|
+
return keep;
|
|
124
|
+
}
|
|
125
|
+
|
|
94
126
|
type ResponsesInputItem =
|
|
127
|
+
| ReplayedReasoning
|
|
95
128
|
| { type: "message"; role: "user" | "assistant"; content: ResponsesContentPart[] }
|
|
96
129
|
| { type: "function_call"; call_id: string; name: string; arguments: string }
|
|
97
130
|
| { type: "function_call_output"; call_id: string; output: string | ResponsesContentPart[] };
|
|
@@ -141,6 +174,7 @@ export function toResponsesInput(
|
|
|
141
174
|
.map((message) => message.content)
|
|
142
175
|
.join("\n\n");
|
|
143
176
|
|
|
177
|
+
const replayed = replayable(messages, provider);
|
|
144
178
|
const input: ResponsesInputItem[] = [];
|
|
145
179
|
for (const message of messages) {
|
|
146
180
|
switch (message.role) {
|
|
@@ -167,12 +201,15 @@ export function toResponsesInput(
|
|
|
167
201
|
break;
|
|
168
202
|
|
|
169
203
|
case "assistant": {
|
|
170
|
-
//
|
|
171
|
-
//
|
|
172
|
-
//
|
|
173
|
-
//
|
|
174
|
-
//
|
|
175
|
-
//
|
|
204
|
+
// `reasoning` (the summary a caller renders) is never replayed: this
|
|
205
|
+
// shape wants the ORIGINAL item back, `rs_…` id and `encrypted_content`
|
|
206
|
+
// blob included, and a synthesized one is rejected. `reasoningItems`
|
|
207
|
+
// are those originals, so they go back first, but only to the provider
|
|
208
|
+
// that made them. Without them the model re-derives its own chain of
|
|
209
|
+
// thought, which is what every stateless caller already lives with.
|
|
210
|
+
if (replayed.has(message)) {
|
|
211
|
+
input.push(...message.reasoningItems!.items.filter(isReplayedReasoning));
|
|
212
|
+
}
|
|
176
213
|
if (message.content) {
|
|
177
214
|
input.push({
|
|
178
215
|
type: "message",
|
|
@@ -213,6 +250,7 @@ interface ResponsesItem {
|
|
|
213
250
|
call_id?: string;
|
|
214
251
|
name?: string;
|
|
215
252
|
arguments?: string;
|
|
253
|
+
encrypted_content?: string;
|
|
216
254
|
}
|
|
217
255
|
|
|
218
256
|
interface ResponsesEvent {
|
|
@@ -350,6 +388,7 @@ export function createResponsesProvider(config: ResponsesConfig): Provider {
|
|
|
350
388
|
store: false,
|
|
351
389
|
};
|
|
352
390
|
if (instructions) request.instructions = instructions;
|
|
391
|
+
if (config.replayReasoning) request.include = ["reasoning.encrypted_content"];
|
|
353
392
|
const maxTokens = opts.maxTokens ?? config.maxTokens;
|
|
354
393
|
if (maxTokens !== undefined) request.max_output_tokens = maxTokens;
|
|
355
394
|
if (opts.temperature !== undefined) request.temperature = opts.temperature;
|
|
@@ -386,6 +425,8 @@ export function createResponsesProvider(config: ResponsesConfig): Provider {
|
|
|
386
425
|
// The cache affinity key. Without one the backend spreads a
|
|
387
426
|
// conversation's turns across cache shards, and a re-sent prefix misses.
|
|
388
427
|
if (opts.sessionId) request.prompt_cache_key = opts.sessionId;
|
|
428
|
+
const serviceTier = opts.serviceTier ?? config.serviceTier;
|
|
429
|
+
if (serviceTier) request.service_tier = serviceTier;
|
|
389
430
|
// Muse Contributor accepts only `auto`. `none` has an exact wire-level
|
|
390
431
|
// equivalent: send no tools. Required/named choices stay explicit and let
|
|
391
432
|
// the endpoint refuse a promise it cannot keep.
|
|
@@ -421,6 +462,11 @@ export function createResponsesProvider(config: ResponsesConfig): Provider {
|
|
|
421
462
|
let sawToolCall = false;
|
|
422
463
|
let wall: ProviderError | undefined;
|
|
423
464
|
let produced = false;
|
|
465
|
+
const reasoningItems: unknown[] = [];
|
|
466
|
+
const reasoningChunk = (): ProviderChunk[] =>
|
|
467
|
+
reasoningItems.length
|
|
468
|
+
? [{ type: "delta", reasoningItems: { provider: id, items: reasoningItems } }]
|
|
469
|
+
: [];
|
|
424
470
|
|
|
425
471
|
for await (const data of streamSse({
|
|
426
472
|
url: apiUrl(baseUrl, config.path ?? DEFAULT_PATH),
|
|
@@ -522,6 +568,12 @@ export function createResponsesProvider(config: ResponsesConfig): Provider {
|
|
|
522
568
|
|
|
523
569
|
case "response.output_item.done": {
|
|
524
570
|
const item = event.item;
|
|
571
|
+
// Kept whole and emitted once, at the end of the turn: it is the
|
|
572
|
+
// backend's own record, and it only means something complete.
|
|
573
|
+
if (config.replayReasoning && item?.type === "reasoning") {
|
|
574
|
+
reasoningItems.push(item);
|
|
575
|
+
break;
|
|
576
|
+
}
|
|
525
577
|
if (item?.type !== "function_call") break;
|
|
526
578
|
const known = pendingFor(item.id, item.call_id);
|
|
527
579
|
if (item.id) pending.delete(item.id);
|
|
@@ -583,6 +635,7 @@ export function createResponsesProvider(config: ResponsesConfig): Provider {
|
|
|
583
635
|
if (response?.status === "failed" || response?.error) {
|
|
584
636
|
throw streamError(id, response.error ?? { message: "response failed" });
|
|
585
637
|
}
|
|
638
|
+
yield* reasoningChunk();
|
|
586
639
|
if (response?.usage) yield usageChunk(response.usage);
|
|
587
640
|
if (response?.status === "incomplete") {
|
|
588
641
|
yield {
|
|
@@ -602,6 +655,7 @@ export function createResponsesProvider(config: ResponsesConfig): Provider {
|
|
|
602
655
|
case "response.incomplete": {
|
|
603
656
|
// A turn that ran out of output tokens still billed for its input,
|
|
604
657
|
// and this event carries usage in the same shape as `completed`.
|
|
658
|
+
yield* reasoningChunk();
|
|
605
659
|
const usage = event.response?.usage;
|
|
606
660
|
if (usage) yield usageChunk(usage);
|
|
607
661
|
yield {
|
package/src/types.ts
CHANGED
|
@@ -108,6 +108,14 @@ export type ChatMessage =
|
|
|
108
108
|
* continuity across a tool round. Absent on every other dialect.
|
|
109
109
|
*/
|
|
110
110
|
reasoningDetails?: unknown[];
|
|
111
|
+
/**
|
|
112
|
+
* The ChatGPT backend's own reasoning items, encrypted, riding back
|
|
113
|
+
* UNMODIFIED on the next turn so the model keeps its chain of thought.
|
|
114
|
+
* Tagged with the provider that made them: only that provider replays
|
|
115
|
+
* them, because another vendor (after a fallback) would reject them.
|
|
116
|
+
* Opaque, like `reasoningDetails`.
|
|
117
|
+
*/
|
|
118
|
+
reasoningItems?: ReasoningItems;
|
|
111
119
|
toolCalls?: ToolCall[];
|
|
112
120
|
}
|
|
113
121
|
| {
|
|
@@ -119,6 +127,12 @@ export type ChatMessage =
|
|
|
119
127
|
images?: ImagePart[];
|
|
120
128
|
};
|
|
121
129
|
|
|
130
|
+
/** Provider-owned reasoning items and the id of the provider that made them. */
|
|
131
|
+
export interface ReasoningItems {
|
|
132
|
+
provider: string;
|
|
133
|
+
items: unknown[];
|
|
134
|
+
}
|
|
135
|
+
|
|
122
136
|
/** JSON Schema for an object — what every provider's tool contract wants. */
|
|
123
137
|
export interface JsonObjectSchema {
|
|
124
138
|
type: "object";
|
|
@@ -192,6 +206,8 @@ export interface ProviderChunk {
|
|
|
192
206
|
/** OpenRouter's normalized reasoning payload — hand it back on the next
|
|
193
207
|
* turn's assistant message verbatim. See ChatMessage.reasoningDetails. */
|
|
194
208
|
reasoningDetails?: unknown[];
|
|
209
|
+
/** Once per turn, where a provider replays its reasoning. See ChatMessage.reasoningItems. */
|
|
210
|
+
reasoningItems?: ReasoningItems;
|
|
195
211
|
toolCalls?: ToolCallDelta[];
|
|
196
212
|
usage?: TokenUsage;
|
|
197
213
|
finishReason?: FinishReason;
|
|
@@ -387,8 +403,19 @@ export interface StreamOptions {
|
|
|
387
403
|
* so it refuses `false` rather than silently ignore it.
|
|
388
404
|
*/
|
|
389
405
|
parallelToolCalls?: boolean;
|
|
406
|
+
/**
|
|
407
|
+
* Which service tier to buy this call on: `priority` is faster and costs
|
|
408
|
+
* more, `flex` is slower and costs less. Sent as `service_tier` on the
|
|
409
|
+
* OpenAI and Responses shapes. Anthropic and Gemini refuse it with an
|
|
410
|
+
* `invalid` error: Anthropic's own `service_tier` means something else, and
|
|
411
|
+
* Gemini has no such switch. Overrides the provider's own setting.
|
|
412
|
+
*/
|
|
413
|
+
serviceTier?: ServiceTier;
|
|
390
414
|
}
|
|
391
415
|
|
|
416
|
+
// ponytail: Codex's `ultrafast` tier (gpt-6-astra only) is the next value to add.
|
|
417
|
+
export type ServiceTier = "priority" | "flex";
|
|
418
|
+
|
|
392
419
|
export interface Provider {
|
|
393
420
|
readonly id: string;
|
|
394
421
|
readonly model: string;
|
|
@@ -406,6 +433,8 @@ export interface Completion {
|
|
|
406
433
|
reasoning: string;
|
|
407
434
|
/** Present only where the provider sent one. See ChatMessage.reasoningDetails. */
|
|
408
435
|
reasoningDetails?: unknown[];
|
|
436
|
+
/** Present only where the provider sent them. See ChatMessage.reasoningItems. */
|
|
437
|
+
reasoningItems?: ReasoningItems;
|
|
409
438
|
usage: TokenUsage;
|
|
410
439
|
finishReason: FinishReason | null;
|
|
411
440
|
model: string;
|
|
@@ -424,6 +453,7 @@ export async function drainStream(
|
|
|
424
453
|
// a drained turn replays only half its own reasoning on the next round —
|
|
425
454
|
// which is the failure `reasoningDetails` exists to prevent.
|
|
426
455
|
let reasoningDetails: unknown[] | undefined;
|
|
456
|
+
let reasoningItems: ReasoningItems | undefined;
|
|
427
457
|
let usage: TokenUsage = EMPTY_USAGE;
|
|
428
458
|
let finishReason: FinishReason | null = null;
|
|
429
459
|
let source: ProviderChunk["source"];
|
|
@@ -433,6 +463,7 @@ export async function drainStream(
|
|
|
433
463
|
if (chunk.content) text += chunk.content;
|
|
434
464
|
if (chunk.reasoning) reasoning += chunk.reasoning;
|
|
435
465
|
if (chunk.reasoningDetails?.length) reasoningDetails = chunk.reasoningDetails;
|
|
466
|
+
if (chunk.reasoningItems?.items.length) reasoningItems = chunk.reasoningItems;
|
|
436
467
|
} else if (chunk.type === "usage" && chunk.usage) {
|
|
437
468
|
usage = chunk.usage;
|
|
438
469
|
} else if (chunk.type === "finish" && chunk.finishReason) {
|
|
@@ -443,6 +474,7 @@ export async function drainStream(
|
|
|
443
474
|
text,
|
|
444
475
|
reasoning,
|
|
445
476
|
...(reasoningDetails ? { reasoningDetails } : {}),
|
|
477
|
+
...(reasoningItems ? { reasoningItems } : {}),
|
|
446
478
|
usage,
|
|
447
479
|
finishReason,
|
|
448
480
|
model: source?.model ?? model,
|
|
@@ -465,11 +497,23 @@ export async function drainStream(
|
|
|
465
497
|
export function stripReasoning(messages: readonly ChatMessage[]): ChatMessage[] {
|
|
466
498
|
return messages.map((message) => {
|
|
467
499
|
if (message.role !== "assistant") return message;
|
|
468
|
-
if (
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
500
|
+
if (
|
|
501
|
+
message.reasoning === undefined &&
|
|
502
|
+
message.reasoningDetails === undefined &&
|
|
503
|
+
message.reasoningItems === undefined
|
|
504
|
+
) {
|
|
505
|
+
return message;
|
|
506
|
+
}
|
|
507
|
+
// Every half goes. `reasoningDetails` and `reasoningItems` are the same
|
|
508
|
+
// chain of thought in the provider's own words, so leaving them behind
|
|
509
|
+
// carries into a thinking-off turn exactly what stripping `reasoning` was
|
|
510
|
+
// meant to keep out.
|
|
511
|
+
const {
|
|
512
|
+
reasoning: _text,
|
|
513
|
+
reasoningDetails: _payload,
|
|
514
|
+
reasoningItems: _items,
|
|
515
|
+
...rest
|
|
516
|
+
} = message;
|
|
473
517
|
return rest;
|
|
474
518
|
});
|
|
475
519
|
}
|