@code-yeongyu/senpi-ai 2026.10.27-2 → 2026.10.30-2
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/dist/api/anthropic-messages.js +6 -4
- package/dist/api/simple-options.js +1 -0
- package/dist/auth/pool/select.d.ts +5 -0
- package/dist/auth/pool/select.js +17 -5
- package/dist/legacy-provider-ids.d.ts +5 -0
- package/dist/legacy-provider-ids.js +7 -0
- package/dist/models.d.ts +5 -0
- package/dist/providers/data/.manifest.json +1 -1
- package/dist/providers/data/amazon-bedrock.json +1 -1
- package/dist/providers/data/chatgpt-subscription.json +1 -1
- package/dist/providers/data/cloudflare-ai-gateway.json +1 -1
- package/dist/providers/data/cloudflare-workers-ai.json +1 -1
- package/dist/providers/data/fireworks.json +1 -1
- package/dist/providers/data/mistral.json +1 -1
- package/dist/providers/data/nvidia.json +1 -1
- package/dist/providers/data/opencode.json +1 -1
- package/dist/providers/data/opengateway.json +1 -1
- package/dist/providers/data/openrouter.json +1 -1
- package/dist/providers/data/radius.json +1 -1
- package/dist/providers/data/venice.json +1 -1
- package/dist/providers/data/vercel-ai-gateway.json +1 -1
- package/dist/types.d.ts +6 -0
- package/dist/utils/provider-diagnostic-sources.d.ts +3 -2
- package/dist/utils/provider-diagnostic-sources.js +15 -4
- package/dist/utils/provider-diagnostic-vocabulary.d.ts +4 -3
- package/dist/utils/provider-diagnostic-vocabulary.js +6 -3
- package/dist/utils/provider-failure-description.js +14 -3
- package/dist/utils/retry.js +8 -3
- package/package.json +2 -2
|
@@ -833,7 +833,7 @@ function consumeLine(text) {
|
|
|
833
833
|
rest: text.slice(nextIndex),
|
|
834
834
|
};
|
|
835
835
|
}
|
|
836
|
-
async function* iterateSseMessages(body, signal) {
|
|
836
|
+
async function* iterateSseMessages(body, signal, onBytes) {
|
|
837
837
|
const reader = body.getReader();
|
|
838
838
|
const decoder = new TextDecoder();
|
|
839
839
|
const state = { event: null, data: [], raw: [] };
|
|
@@ -847,6 +847,8 @@ async function* iterateSseMessages(body, signal) {
|
|
|
847
847
|
if (done) {
|
|
848
848
|
break;
|
|
849
849
|
}
|
|
850
|
+
if (value.byteLength > 0)
|
|
851
|
+
onBytes?.();
|
|
850
852
|
buffer += decoder.decode(value, { stream: true });
|
|
851
853
|
let consumed = consumeLine(buffer);
|
|
852
854
|
while (consumed) {
|
|
@@ -883,13 +885,13 @@ async function* iterateSseMessages(body, signal) {
|
|
|
883
885
|
reader.releaseLock();
|
|
884
886
|
}
|
|
885
887
|
}
|
|
886
|
-
async function* iterateAnthropicEvents(response, signal) {
|
|
888
|
+
async function* iterateAnthropicEvents(response, signal, onTransportActivity) {
|
|
887
889
|
if (!response.body) {
|
|
888
890
|
throw new Error("Attempted to iterate over an Anthropic response with no body");
|
|
889
891
|
}
|
|
890
892
|
let sawMessageStart = false;
|
|
891
893
|
let sawMessageEnd = false;
|
|
892
|
-
for await (const sse of iterateSseMessages(response.body, signal)) {
|
|
894
|
+
for await (const sse of iterateSseMessages(response.body, signal, onTransportActivity)) {
|
|
893
895
|
if (sse.event === "error") {
|
|
894
896
|
let errorText = sse.data;
|
|
895
897
|
const hintMs = extract429RetryAfterMs({ bodyText: sse.data });
|
|
@@ -1141,7 +1143,7 @@ export const stream = (model, context, options) => {
|
|
|
1141
1143
|
await options?.onResponse?.({ status: response.status, headers: headersToRecord(response.headers) }, model);
|
|
1142
1144
|
stream.push({ type: "start", partial: output });
|
|
1143
1145
|
const blocks = output.content;
|
|
1144
|
-
for await (const event of iterateAnthropicEvents(response, requestSignal)) {
|
|
1146
|
+
for await (const event of iterateAnthropicEvents(response, requestSignal, options?.onTransportActivity)) {
|
|
1145
1147
|
await options?.onProviderStreamEvent?.(event, model);
|
|
1146
1148
|
if (event.type === "message_start") {
|
|
1147
1149
|
output.responseId = event.message.id;
|
|
@@ -123,6 +123,7 @@ export function buildBaseOptions(model, context, options, apiKey) {
|
|
|
123
123
|
onPayload: options?.onPayload,
|
|
124
124
|
onResponse: options?.onResponse,
|
|
125
125
|
onProviderStreamEvent: options?.onProviderStreamEvent,
|
|
126
|
+
onTransportActivity: options?.onTransportActivity,
|
|
126
127
|
timeoutMs: options?.timeoutMs,
|
|
127
128
|
websocketConnectTimeoutMs: options?.websocketConnectTimeoutMs,
|
|
128
129
|
maxRetries: options?.maxRetries,
|
|
@@ -36,6 +36,11 @@ export declare function applyAccountOrder<T extends {
|
|
|
36
36
|
export declare function withoutOrderedAccount<T extends {
|
|
37
37
|
order?: string[];
|
|
38
38
|
}>(credential: T, name: string): T;
|
|
39
|
+
/**
|
|
40
|
+
* Whether selection skips a slot now: an auth block holds until a login rewrites the slot, whatever its deadline;
|
|
41
|
+
* any other block holds until its deadline. Account listings ask this too, so they never call a skipped slot available.
|
|
42
|
+
*/
|
|
43
|
+
export declare function isSlotBlocked(slot: Pick<SelectableSlot, "blockedUntil" | "blockReason">, now: number): boolean;
|
|
39
44
|
/** Elapsed rate/capacity blocks clear; auth blocks persist until a login rewrites the slot. */
|
|
40
45
|
export declare function clearExpiredSlotBlocks(slots: readonly SelectableSlot[], now?: number): SelectableSlot[];
|
|
41
46
|
/** Selects a pinned or HRW-ranked slot with no pool-global selection state. */
|
package/dist/auth/pool/select.js
CHANGED
|
@@ -1,9 +1,17 @@
|
|
|
1
1
|
export const DEFAULT_POOL_AFFINITY_KEY = "credential-pool-default";
|
|
2
2
|
export class AllSlotsBlockedError extends Error {
|
|
3
3
|
constructor(soonestUnblockAt) {
|
|
4
|
+
// Local time for the reader, the ISO instant for prose scanners, and one step (senpi#4329).
|
|
4
5
|
super(soonestUnblockAt === undefined
|
|
5
|
-
? "All credential slots are blocked until re-login."
|
|
6
|
-
: `All credential slots are blocked until ${new Date(soonestUnblockAt).
|
|
6
|
+
? "All credential slots are blocked until re-login. Sign in again or add a new API key for this provider, or choose another model."
|
|
7
|
+
: `All credential slots are blocked until ${new Date(soonestUnblockAt).toLocaleString("en-US", {
|
|
8
|
+
year: "numeric",
|
|
9
|
+
month: "short",
|
|
10
|
+
day: "numeric",
|
|
11
|
+
hour: "numeric",
|
|
12
|
+
minute: "2-digit",
|
|
13
|
+
timeZoneName: "short",
|
|
14
|
+
})} (${new Date(soonestUnblockAt).toISOString()}). Wait until then, or choose another model.`);
|
|
7
15
|
this.name = "AllSlotsBlockedError";
|
|
8
16
|
this.soonestUnblockAt = soonestUnblockAt;
|
|
9
17
|
}
|
|
@@ -51,7 +59,11 @@ export function withoutOrderedAccount(credential, name) {
|
|
|
51
59
|
next.order = order;
|
|
52
60
|
return next;
|
|
53
61
|
}
|
|
54
|
-
|
|
62
|
+
/**
|
|
63
|
+
* Whether selection skips a slot now: an auth block holds until a login rewrites the slot, whatever its deadline;
|
|
64
|
+
* any other block holds until its deadline. Account listings ask this too, so they never call a skipped slot available.
|
|
65
|
+
*/
|
|
66
|
+
export function isSlotBlocked(slot, now) {
|
|
55
67
|
return slot.blockReason === "auth_error" || (slot.blockedUntil !== undefined && slot.blockedUntil > now);
|
|
56
68
|
}
|
|
57
69
|
/** Elapsed rate/capacity blocks clear; auth blocks persist until a login rewrites the slot. */
|
|
@@ -66,9 +78,9 @@ export function clearExpiredSlotBlocks(slots, now = Date.now()) {
|
|
|
66
78
|
}
|
|
67
79
|
function selectUnblocked(slots, options, now) {
|
|
68
80
|
const pinned = options.pinnedSlot === undefined ? undefined : slots.find((slot) => slot.name === options.pinnedSlot);
|
|
69
|
-
if (pinned && !
|
|
81
|
+
if (pinned && !isSlotBlocked(pinned, now))
|
|
70
82
|
return pinned;
|
|
71
|
-
return rendezvousOrder(getPoolAffinityKey(options), slots, options.hasher).find((slot) => !
|
|
83
|
+
return rendezvousOrder(getPoolAffinityKey(options), slots, options.hasher).find((slot) => !isSlotBlocked(slot, now));
|
|
72
84
|
}
|
|
73
85
|
function soonestUnblockAt(slots, now) {
|
|
74
86
|
const candidates = slots
|
|
@@ -11,6 +11,11 @@ export declare function isLegacyProviderId(id: string): boolean;
|
|
|
11
11
|
* earlier version. Returns an empty array for an id that was never renamed.
|
|
12
12
|
*/
|
|
13
13
|
export declare function legacyProviderIdsFor(canonicalId: string): string[];
|
|
14
|
+
/**
|
|
15
|
+
* The id upstream pi still uses for `providerId`: the legacy spelling of a renamed provider (the
|
|
16
|
+
* remote model catalog serves `openai-codex`, not `chatgpt-subscription`), the id itself otherwise.
|
|
17
|
+
*/
|
|
18
|
+
export declare function upstreamProviderIdFor(providerId: string): string;
|
|
14
19
|
/**
|
|
15
20
|
* Read `record` by provider id, trying the canonical key first and then every
|
|
16
21
|
* legacy spelling that normalizes to it. Never throws and never rewrites.
|
|
@@ -33,6 +33,13 @@ export function legacyProviderIdsFor(canonicalId) {
|
|
|
33
33
|
.filter(([, canonical]) => canonical === canonicalId)
|
|
34
34
|
.map(([legacy]) => legacy);
|
|
35
35
|
}
|
|
36
|
+
/**
|
|
37
|
+
* The id upstream pi still uses for `providerId`: the legacy spelling of a renamed provider (the
|
|
38
|
+
* remote model catalog serves `openai-codex`, not `chatgpt-subscription`), the id itself otherwise.
|
|
39
|
+
*/
|
|
40
|
+
export function upstreamProviderIdFor(providerId) {
|
|
41
|
+
return legacyProviderIdsFor(providerId)[0] ?? providerId;
|
|
42
|
+
}
|
|
36
43
|
/**
|
|
37
44
|
* Read `record` by provider id, trying the canonical key first and then every
|
|
38
45
|
* legacy spelling that normalizes to it. Never throws and never rewrites.
|
package/dist/models.d.ts
CHANGED
|
@@ -66,6 +66,11 @@ type ProviderModel<TApi extends Api> = Model<TApi> | ImageModel<ImageApi> | Clas
|
|
|
66
66
|
export interface Provider<TApi extends Api = Api> {
|
|
67
67
|
readonly id: string;
|
|
68
68
|
readonly name: string;
|
|
69
|
+
/**
|
|
70
|
+
* The provider's endpoint. Transports send each request to `model.baseUrl`, never to this value or a
|
|
71
|
+
* hard-coded host: senpi sets every model's `baseUrl` (and this field) to a user's models.json
|
|
72
|
+
* `baseUrl` when one is configured for the provider.
|
|
73
|
+
*/
|
|
69
74
|
readonly baseUrl?: string;
|
|
70
75
|
readonly headers?: ProviderHeaders;
|
|
71
76
|
/** Omitting it means the shipped senpi default profile applies. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"schemaVersion":6,"generatedAt":"2026-10-
|
|
1
|
+
{"schemaVersion":6,"generatedAt":"2026-10-30T18:20:52.997Z","structureHash":"536b4b653e1cd2b06b71fd0a91cc07d21a2e207657c728557c82e512ac304180","files":{"alibaba-token-plan.json":"9f4d42cc765b2a875bcdcaa392bfdc1323c88968309baf9db9f611a26c595c73","amazon-bedrock.json":"a7e20838b095788cb9d5acbde49664b7f2868fad9b10fb4bbbd66635085bca2d","ant-ling.json":"628feab3ef6c7d80ce96f75999804e1d9f05fec3851e5302e30fff3b69db90f6","anthropic.json":"111384e9f5845e0247bd34edf2400ef7720fcbdac140b944cf8878192d8bd6ab","azure-openai-responses.json":"fd64c6a2a99fa87d768ed2f75f5c7813876df1dbe2f0d1776f7d9bd7ce2e61e4","bai.json":"7e6861aaf27d8af34e1771c6092602194fcec0a0046e6adfab9c3ee54d32b99f","baseten.json":"5bd3c98c61769388b3724368ef964d16b7a58e41a6edcf4ebceb9d4ac0a21d3b","cerebras.json":"2eb969b2956844f9a641d3de52c19f1791293c58f607282fb3b0ebfe85ff66f8","chatgpt-subscription.json":"98320f1d51709d26ca047b3d6e3191cfe2f192f1b27897cbb492f47b0e5a5030","cloudflare-ai-gateway.json":"1faa40d07413f2dd8a4c387dcae2c8d084e222d3191730f5bd4af48b2fa6ba34","cloudflare-workers-ai.json":"1b9ad4360bde817f0ea358431020308f1052840e2278a57a93629f1790072d2e","deepseek.json":"10a296fb3e898f7715c80890c8af0af4f5fd58f22dbcd6612fdc5d762157ccdc","fireworks.json":"56c557eb97573028e871c211cb17e1d60665eb12d67a712615db4bb255bc9253","github-copilot.json":"474fa92eb88ef242208ca93709ebe6d7f21754917d0cad6210bd1edf2b99a87e","google-vertex.json":"8f986b4aca8c685b0f3481bd252bda21e4858bf9015ad37fc5bcc44b7f01d076","google.json":"d25613a7377e81d785d571be514d67b35436511ce6946247dc66f8674e2a5035","groq.json":"087df37206e9eed8b10f1e558e4c496289bef60074b7e8afba4f20fb102b4ec2","huggingface.json":"209cdcf205961ef2ff203486da614232a86a069ebe0e98266189a14a235fac8f","meta.json":"563f5ec07973f52ab8c3eb26ef91a36ad8a79e950b37174935666cdcb624bf35","minimax-cn.json":"70df757e95e9ea2275bf451956b58a746020835f6cbcbd1a644d7fad5b9941d3","minimax.json":"747038b05f4fc24e41405e8bc810f893d7d5121e214e35e6f812f775769a3806","mistral.json":"aafecc66686645b1baa8c90e81c02856115f09999ff507f9bee004d2e1c77a28","moonshotai-cn.json":"a953eace729ef96c91195d477c63c776a1c40aa36085b61236ca1be0fe5b9337","moonshotai.json":"0435f9dc4e532c0764b6d33d8dfa83ce5fccdbcab54224c5ae74ec2d965290f5","nvidia.json":"0e45bcd4d5a897f6f6785c0e257a9fb03691cef3ff87d9254da5daa5075468da","openai.json":"d61200cdd22d931dfff8181a5ed8d636c89fb34637dcec80f07ec24f8c2ef5c6","opencode-go.json":"2a5e0424854edfc0ef9a49033e80a8eb32c01a98c56ba2879b36a9502baa4027","opencode.json":"0e08d3fc23284852968106c4e0c17e85adedf729db145b8c7871e9bd999845b1","opengateway.json":"92262b093ece3a900faccc547e5dc19688548e7e41c360ddb657b2e9deb36e98","openrouter.json":"0115939af894fec1b173573c9a9d7f22a643ec3a2063d5515973cb873ed2c01b","qwen-token-plan-cn.json":"751b18bd443fceb16659ea33013fed90fbf3bbdb4e0ffd7607c93cee2b4cf40d","qwen-token-plan-individual.json":"35ffb0ba1a1ce3f68fc73262dd5e07fbcb5bf5911ad1e6e17bc0621e2894676a","qwen-token-plan.json":"e59b0a45e238578f80d0eea937a093b9cec7cb93751ae20a9e88b16c09eb5cdd","radius.json":"5241be199f873fd7474b1ccb1fc911af223903af681909029fefe9fb8b49f83c","together.json":"a68b8386a2f28f9e1570d296223fdbdc0ebcca8a322ade5b999212c0e60b772c","typesafe.json":"83409437dbf4c353ac413c0db4e7cf29902fc35e0e0197bdb4eeda2d7f3b07cd","venice.json":"b835ee2e486477dc306d8e82accc06fe8a4bb708471664530e5681811174072d","vercel-ai-gateway.json":"7ae8df20eff08fa919f5aa3b04298fe1eb4614f6a3e62f64000645cab2120279","xai.json":"cf3f475fe5a13625f858171900ba3456c4d95ebb0bac5060cc25e29bb31d3457","xiaomi-token-plan-ams.json":"7fa339ba5675460b1ff4e5bca9e8ad0aa60b0277d75a989b1c196aa6ca2af34d","xiaomi-token-plan-cn.json":"ef7917c6ea0d3edc655c8a52082ee5f1330658aa6f10199d8f164eb4605ef0a0","xiaomi-token-plan-sgp.json":"addda93f6b70ba879db97e3cfd2e4f2efc7cda47d7daa78c23c5b0e51162de5c","xiaomi.json":"5a42caf49d3e4d7946d60386e9899c2146bf8e3cbaff4c576ee4f49b398cc11c","zai-coding-cn.json":"2b6f85e04b5c568e320b1bde25b1c1614af9342e4c1ba0e1d5bb248aaf50cfe9","zai.json":"e9cc408e1756de8abc70148d63d89d11a63c65b7ef06ad0661cd1328e4209ba3"}}
|