@code-yeongyu/senpi-ai 2026.10.26-2 → 2026.10.27-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.
Files changed (34) hide show
  1. package/dist/index.d.ts +2 -0
  2. package/dist/index.js +2 -0
  3. package/dist/providers/data/.manifest.json +1 -1
  4. package/dist/providers/data/azure-openai-responses.json +1 -1
  5. package/dist/providers/data/github-copilot.json +1 -1
  6. package/dist/providers/data/huggingface.json +1 -1
  7. package/dist/providers/data/moonshotai-cn.json +1 -1
  8. package/dist/providers/data/moonshotai.json +1 -1
  9. package/dist/providers/data/openai.json +1 -1
  10. package/dist/providers/data/opencode.json +1 -1
  11. package/dist/providers/data/opengateway.json +1 -1
  12. package/dist/providers/data/openrouter.json +1 -1
  13. package/dist/providers/data/radius.json +1 -1
  14. package/dist/providers/data/venice.json +1 -1
  15. package/dist/providers/data/vercel-ai-gateway.json +1 -1
  16. package/dist/types.d.ts +12 -1
  17. package/dist/utils/assistant-capture-record.d.ts +13 -0
  18. package/dist/utils/assistant-capture-record.js +163 -0
  19. package/dist/utils/assistant-capture.d.ts +10 -4
  20. package/dist/utils/assistant-capture.js +2 -159
  21. package/dist/utils/assistant-seed-frontier.d.ts +7 -0
  22. package/dist/utils/assistant-seed-frontier.js +14 -0
  23. package/dist/utils/event-stream.d.ts +1 -0
  24. package/dist/utils/event-stream.js +13 -0
  25. package/dist/utils/provider-error.d.ts +12 -0
  26. package/dist/utils/provider-error.js +28 -0
  27. package/dist/utils/retry.js +3 -0
  28. package/dist/utils/secret-classifier.d.ts +28 -0
  29. package/dist/utils/secret-classifier.js +258 -0
  30. package/dist/utils/secret-lexer.d.ts +51 -0
  31. package/dist/utils/secret-lexer.js +295 -0
  32. package/dist/utils/secret-redaction.d.ts +13 -0
  33. package/dist/utils/secret-redaction.js +198 -0
  34. package/package.json +2 -2
package/dist/types.d.ts CHANGED
@@ -569,10 +569,16 @@ export interface AssistantMessage {
569
569
  stopDetails?: AssistantStopDetails;
570
570
  deferred?: DeferredHandle;
571
571
  errorMessage?: string;
572
- /** Redaction-safe cause retained when a session projects a provider limit as user-facing advice. */
572
+ /**
573
+ * Redaction-safe cause: a provider limit projected as user-facing advice, or a model id its route does not
574
+ * serve (`model_config`, never retried or fallen back).
575
+ */
573
576
  errorCause?: {
574
577
  type: "usage_limit";
575
578
  scope: "account" | "model";
579
+ } | {
580
+ type: "model_config";
581
+ model: string;
576
582
  };
577
583
  /** Structured provider failure family, minted only by a provider adapter from structured error metadata. */
578
584
  providerDiagnostic?: ProviderDiagnostic;
@@ -583,6 +589,11 @@ export interface AssistantMessage {
583
589
  * No model produced the failure, so retry and model fallback must not act on it.
584
590
  */
585
591
  failureSource?: "local";
592
+ /**
593
+ * Set by the model runtime on a failed response when the same credential (provider, endpoint and key) served a
594
+ * response moments before: an `auth` failure then means the provider refused this request, not the key.
595
+ */
596
+ credentialServedRecently?: true;
586
597
  rawStopReason?: string;
587
598
  /**
588
599
  * Provider indication of whether the model explicitly ended its turn.
@@ -0,0 +1,13 @@
1
+ import type { AssistantMessage, AssistantMessageEvent } from "../types.ts";
2
+ import type { AssistantCapture, CaptureHint, CaptureState } from "./assistant-capture.ts";
3
+ type PublicBlock = AssistantMessage["content"][number];
4
+ export declare function freezeCapture<T>(value: T): T;
5
+ /** Only declared public fields survive; provider scratch and symbols never enter a seed. */
6
+ export declare function publicBlock(block: PublicBlock): PublicBlock;
7
+ /** Structural equality over plain data; any other object counts as changed, so a reused copy is never stale. */
8
+ export declare function sameValue(left: unknown, right: unknown): boolean;
9
+ /** A terminal replaced downstream: its usage and message, plus every scalar that differs from the source terminal. */
10
+ export declare function terminalCapture(capture: AssistantCapture, terminal: AssistantMessage): AssistantCapture;
11
+ export declare function buildCapture(state: CaptureState, event: AssistantMessageEvent, hint: CaptureHint): AssistantCapture | undefined;
12
+ export {};
13
+ //# sourceMappingURL=assistant-capture-record.d.ts.map
@@ -0,0 +1,163 @@
1
+ import { seedNextOffset } from "./assistant-seed-frontier.js";
2
+ const NO_CHANGE = Object.freeze({});
3
+ export function freezeCapture(value) {
4
+ const freeze = (part) => {
5
+ if (part === null || typeof part !== "object" || Object.isFrozen(part))
6
+ return;
7
+ for (const child of Object.values(part))
8
+ freeze(child);
9
+ Object.freeze(part);
10
+ };
11
+ const copy = structuredClone(value);
12
+ freeze(copy);
13
+ return copy;
14
+ }
15
+ /** Only declared public fields survive; provider scratch and symbols never enter a seed. */
16
+ export function publicBlock(block) {
17
+ const fields = {
18
+ text: ["type", "text", "audience", "textSignature"],
19
+ thinking: ["type", "thinking", "thinkingSignature", "redacted", "startedAt", "endedAt"],
20
+ toolCall: ["type", "id", "name", "arguments", "incomplete", "errorMessage", "thoughtSignature", "namespace"],
21
+ providerNative: ["type", "subtype", "raw"],
22
+ };
23
+ return Object.fromEntries(fields[block.type].filter((key) => key in block).map((key) => [key, Reflect.get(block, key)]));
24
+ }
25
+ /** Structural equality over plain data; any other object counts as changed, so a reused copy is never stale. */
26
+ export function sameValue(left, right) {
27
+ if (left === right)
28
+ return true;
29
+ if (!isPlainData(left) || !isPlainData(right) || Array.isArray(left) !== Array.isArray(right))
30
+ return false;
31
+ const keys = Object.keys(left);
32
+ return (keys.length === Object.keys(right).length &&
33
+ keys.every((key) => Object.hasOwn(right, key) && sameValue(Reflect.get(left, key), Reflect.get(right, key))));
34
+ }
35
+ function isPlainData(value) {
36
+ if (typeof value !== "object" || value === null)
37
+ return false;
38
+ const prototype = Object.getPrototypeOf(value);
39
+ return prototype === Object.prototype || prototype === Array.prototype || prototype === null;
40
+ }
41
+ /** A terminal replaced downstream: its usage and message, plus every scalar that differs from the source terminal. */
42
+ export function terminalCapture(capture, terminal) {
43
+ const metadata = { ...capture.metadata };
44
+ const previous = capture.message ?? {};
45
+ for (const key of new Set([...Object.keys(previous), ...Object.keys(terminal)])) {
46
+ if (key === "content" || key === "usage")
47
+ continue;
48
+ const value = Reflect.get(terminal, key);
49
+ if (!sameValue(value, Reflect.get(previous, key)))
50
+ metadata[key] = key in terminal ? value : null;
51
+ }
52
+ return Object.freeze({
53
+ usage: frozenUsage(terminal.usage),
54
+ metadata: freezeCapture(metadata),
55
+ message: freezeCapture({ ...terminal, content: terminal.content.map(publicBlock) }),
56
+ ...(capture.rewrittenFrom === undefined ? {} : { rewrittenFrom: capture.rewrittenFrom }),
57
+ });
58
+ }
59
+ export function buildCapture(state, event, hint) {
60
+ const message = event.type === "done" ? event.message : event.type === "error" ? event.error : event.partial;
61
+ const usage = usageCopy(state, message.usage);
62
+ if (!("contentIndex" in event)) {
63
+ return Object.freeze({
64
+ usage,
65
+ metadata: changedMetadata(state, message),
66
+ message: freezeCapture({ ...message, content: message.content.map(publicBlock) }),
67
+ ...(hint.rewrittenFrom === undefined ? {} : { rewrittenFrom: hint.rewrittenFrom }),
68
+ });
69
+ }
70
+ const index = hint.contentIndex ?? event.contentIndex;
71
+ const own = index === event.contentIndex ? {} : { contentIndex: index };
72
+ const carried = hint.carried;
73
+ if ("delta" in event) {
74
+ if (carried) {
75
+ const { parsedThrough } = carried;
76
+ return Object.freeze({
77
+ usage,
78
+ offset: carried.offset,
79
+ ...(parsedThrough === undefined ? {} : { parsedThrough }),
80
+ });
81
+ }
82
+ const position = state.positions.get(index) ?? { offset: 0, parsedThrough: 0 };
83
+ const offset = hint.offset ?? position.offset;
84
+ position.offset = offset + event.delta.length;
85
+ if (event.type === "toolcall_delta" && event.delta.length > 0) {
86
+ position.parsedThrough = hint.parsedThrough ?? Math.max(position.parsedThrough, position.offset);
87
+ }
88
+ state.positions.set(index, position);
89
+ return Object.freeze({
90
+ usage,
91
+ offset,
92
+ ...own,
93
+ ...(event.type === "toolcall_delta" ? { parsedThrough: position.parsedThrough } : {}),
94
+ });
95
+ }
96
+ const metadata = changedMetadata(state, message);
97
+ if (carried) {
98
+ const { usage: _usage, metadata: _metadata, message: _message, offset: _offset, ...facts } = carried;
99
+ return Object.freeze({ ...facts, usage, metadata });
100
+ }
101
+ const starts = event.type === "text_start" || event.type === "thinking_start" || event.type === "toolcall_start";
102
+ const source = event.type === "toolcall_end" ? event.toolCall : message.content[index];
103
+ // A start announced before its block is appended has no seed to freeze yet.
104
+ if (starts && source === undefined)
105
+ return undefined;
106
+ const block = freezeCapture(publicBlock(source));
107
+ if (!starts)
108
+ return Object.freeze({ usage, metadata, block, ...own });
109
+ const rawArgumentPrefix = hint.rawArgumentPrefix ?? "";
110
+ const coveredCodeUnits = block.type === "text"
111
+ ? block.text.length
112
+ : block.type === "thinking"
113
+ ? block.thinking.length
114
+ : rawArgumentPrefix.length;
115
+ const parsedThrough = hint.parsedThrough ?? 0;
116
+ const nextOffset = seedNextOffset(hint.offset ?? coveredCodeUnits, coveredCodeUnits);
117
+ state.positions.set(index, { offset: nextOffset, parsedThrough });
118
+ return Object.freeze({
119
+ usage,
120
+ metadata,
121
+ block,
122
+ coveredCodeUnits,
123
+ nextOffset,
124
+ ...own,
125
+ ...(block.type === "toolCall" ? { rawArgumentPrefix, parsedThrough } : {}),
126
+ });
127
+ }
128
+ function usageCopy(state, usage) {
129
+ if (state.usage !== undefined && sameValue(usage, state.usage))
130
+ return state.usage;
131
+ state.usage = frozenUsage(usage);
132
+ return state.usage;
133
+ }
134
+ function frozenUsage(usage) {
135
+ return Object.freeze({
136
+ ...usage,
137
+ cost: Object.freeze({ ...usage.cost }),
138
+ ...(usage.contextUsage ? { contextUsage: Object.freeze({ ...usage.contextUsage }) } : {}),
139
+ });
140
+ }
141
+ /** Scalar message fields changed since the last non-delta capture; the baseline moves only once its copy exists. */
142
+ function changedMetadata(state, message) {
143
+ const changed = {};
144
+ for (const key of Object.keys(message)) {
145
+ if (key === "content" || key === "usage")
146
+ continue;
147
+ const value = Reflect.get(message, key);
148
+ if (!state.metadata.has(key) || state.metadata.get(key) !== value)
149
+ changed[key] = value;
150
+ }
151
+ for (const key of state.metadata.keys())
152
+ if (!(key in message))
153
+ changed[key] = null;
154
+ const frozen = Object.keys(changed).length === 0 ? NO_CHANGE : freezeCapture(changed);
155
+ for (const key of Object.keys(changed)) {
156
+ if (key in message)
157
+ state.metadata.set(key, Reflect.get(message, key));
158
+ else
159
+ state.metadata.delete(key);
160
+ }
161
+ return frozen;
162
+ }
163
+ //# sourceMappingURL=assistant-capture-record.js.map
@@ -1,4 +1,5 @@
1
1
  import type { AssistantMessage, AssistantMessageEvent, Usage } from "../types.ts";
2
+ export { freezeCapture, publicBlock } from "./assistant-capture-record.ts";
2
3
  type PublicBlock = AssistantMessage["content"][number];
3
4
  export type AssistantProgress = {
4
5
  type: "message_block_state";
@@ -39,6 +40,8 @@ export interface AssistantCapture {
39
40
  readonly message?: AssistantMessage;
40
41
  readonly rawArgumentPrefix?: string;
41
42
  readonly coveredCodeUnits?: number;
43
+ /** Required on block starts: zero for replay, full seed length for continuation. */
44
+ readonly nextOffset?: number;
42
45
  readonly offset?: number;
43
46
  readonly parsedThrough?: number;
44
47
  /** The block's own index where the event's public `contentIndex` names another block; internal readers prefer it. */
@@ -51,6 +54,13 @@ export interface AssistantCapture {
51
54
  */
52
55
  readonly rewrittenFrom?: number;
53
56
  }
57
+ export interface CaptureState {
58
+ positions: Map<number, CapturePosition>;
59
+ metadata: Map<string, unknown>;
60
+ usage?: Usage;
61
+ insertCopies: Map<number, PublicBlock>;
62
+ failedBlocks: Set<number>;
63
+ }
54
64
  /** While held, every capture-enabled stream whose first push comes after it builds capture records. */
55
65
  export declare function subscribeAssistantCapture(): () => void;
56
66
  export declare function setAssistantReader(owner: object, reader: QueueReader): void;
@@ -90,12 +100,8 @@ export interface CapturePosition {
90
100
  offset: number;
91
101
  parsedThrough: number;
92
102
  }
93
- export declare function freezeCapture<T>(value: T): T;
94
- /** Only declared public fields survive; provider scratch and symbols never enter a seed. */
95
- export declare function publicBlock(block: PublicBlock): PublicBlock;
96
103
  export declare function dropAssistantCapture(event: object): void;
97
104
  export declare function carryAssistantCapture<T extends object>(source: object, target: T, terminal?: AssistantMessage): T;
98
105
  /** Never parses or traverses a block on a delta, including a no-growth delta. */
99
106
  export declare function captureAssistantEvent(owner: object, event: AssistantMessageEvent, hint?: CaptureHint): void;
100
- export {};
101
107
  //# sourceMappingURL=assistant-capture.d.ts.map
@@ -1,3 +1,5 @@
1
+ import { buildCapture, freezeCapture, publicBlock, sameValue, terminalCapture } from "./assistant-capture-record.js";
2
+ export { freezeCapture, publicBlock } from "./assistant-capture-record.js";
1
3
  const captures = new WeakMap();
2
4
  const readers = new WeakMap();
3
5
  const writers = new WeakMap();
@@ -5,7 +7,6 @@ const writers = new WeakMap();
5
7
  const states = new WeakMap();
6
8
  const demanded = new WeakSet();
7
9
  const warned = new WeakSet();
8
- const NO_CHANGE = Object.freeze({});
9
10
  let subscribers = 0;
10
11
  let capturedAny = false;
11
12
  /** While held, every capture-enabled stream whose first push comes after it builds capture records. */
@@ -132,44 +133,6 @@ export function isAssistantProgress(event) {
132
133
  event.type === "message_block_metadata" ||
133
134
  event.type === "message_tool_arguments_reset");
134
135
  }
135
- export function freezeCapture(value) {
136
- const freeze = (part) => {
137
- if (part === null || typeof part !== "object" || Object.isFrozen(part))
138
- return;
139
- for (const child of Object.values(part))
140
- freeze(child);
141
- Object.freeze(part);
142
- };
143
- const copy = structuredClone(value);
144
- freeze(copy);
145
- return copy;
146
- }
147
- /** Only declared public fields survive; provider scratch and symbols never enter a seed. */
148
- export function publicBlock(block) {
149
- const fields = {
150
- text: ["type", "text", "audience", "textSignature"],
151
- thinking: ["type", "thinking", "thinkingSignature", "redacted", "startedAt", "endedAt"],
152
- toolCall: ["type", "id", "name", "arguments", "incomplete", "errorMessage", "thoughtSignature", "namespace"],
153
- providerNative: ["type", "subtype", "raw"],
154
- };
155
- return Object.fromEntries(fields[block.type].filter((key) => key in block).map((key) => [key, Reflect.get(block, key)]));
156
- }
157
- /** Structural equality over plain data; any other object counts as changed, so a reused copy is never stale. */
158
- function sameValue(left, right) {
159
- if (left === right)
160
- return true;
161
- if (!isPlainData(left) || !isPlainData(right) || Array.isArray(left) !== Array.isArray(right))
162
- return false;
163
- const keys = Object.keys(left);
164
- return (keys.length === Object.keys(right).length &&
165
- keys.every((key) => Object.hasOwn(right, key) && sameValue(Reflect.get(left, key), Reflect.get(right, key))));
166
- }
167
- function isPlainData(value) {
168
- if (typeof value !== "object" || value === null)
169
- return false;
170
- const prototype = Object.getPrototypeOf(value);
171
- return prototype === Object.prototype || prototype === Array.prototype || prototype === null;
172
- }
173
136
  export function dropAssistantCapture(event) {
174
137
  if (capturedAny)
175
138
  captures.delete(event);
@@ -186,24 +149,6 @@ export function carryAssistantCapture(source, target, terminal) {
186
149
  }
187
150
  return target;
188
151
  }
189
- /** A terminal replaced downstream: its usage and message, plus every scalar that differs from the source terminal. */
190
- function terminalCapture(capture, terminal) {
191
- const metadata = { ...capture.metadata };
192
- const previous = capture.message ?? {};
193
- for (const key of new Set([...Object.keys(previous), ...Object.keys(terminal)])) {
194
- if (key === "content" || key === "usage")
195
- continue;
196
- const value = Reflect.get(terminal, key);
197
- if (!sameValue(value, Reflect.get(previous, key)))
198
- metadata[key] = key in terminal ? value : null;
199
- }
200
- return Object.freeze({
201
- usage: frozenUsage(terminal.usage),
202
- metadata: freezeCapture(metadata),
203
- message: freezeCapture({ ...terminal, content: terminal.content.map(publicBlock) }),
204
- ...(capture.rewrittenFrom === undefined ? {} : { rewrittenFrom: capture.rewrittenFrom }),
205
- });
206
- }
207
152
  /** Never parses or traverses a block on a delta, including a no-growth delta. */
208
153
  export function captureAssistantEvent(owner, event, hint = {}) {
209
154
  const state = activeState(owner);
@@ -222,106 +167,4 @@ export function captureAssistantEvent(owner, event, hint = {}) {
222
167
  dropCapture(owner, error, state, contentIndex);
223
168
  }
224
169
  }
225
- function buildCapture(state, event, hint) {
226
- const message = event.type === "done" ? event.message : event.type === "error" ? event.error : event.partial;
227
- const usage = usageCopy(state, message.usage);
228
- if (!("contentIndex" in event)) {
229
- return Object.freeze({
230
- usage,
231
- metadata: changedMetadata(state, message),
232
- message: freezeCapture({ ...message, content: message.content.map(publicBlock) }),
233
- ...(hint.rewrittenFrom === undefined ? {} : { rewrittenFrom: hint.rewrittenFrom }),
234
- });
235
- }
236
- const index = hint.contentIndex ?? event.contentIndex;
237
- const own = index === event.contentIndex ? {} : { contentIndex: index };
238
- const carried = hint.carried;
239
- if ("delta" in event) {
240
- if (carried) {
241
- const { parsedThrough } = carried;
242
- return Object.freeze({
243
- usage,
244
- offset: carried.offset,
245
- ...(parsedThrough === undefined ? {} : { parsedThrough }),
246
- });
247
- }
248
- const position = state.positions.get(index) ?? { offset: 0, parsedThrough: 0 };
249
- const offset = hint.offset ?? position.offset;
250
- position.offset = offset + event.delta.length;
251
- if (event.type === "toolcall_delta" && event.delta.length > 0) {
252
- position.parsedThrough = hint.parsedThrough ?? Math.max(position.parsedThrough, position.offset);
253
- }
254
- state.positions.set(index, position);
255
- return Object.freeze({
256
- usage,
257
- offset,
258
- ...own,
259
- ...(event.type === "toolcall_delta" ? { parsedThrough: position.parsedThrough } : {}),
260
- });
261
- }
262
- const metadata = changedMetadata(state, message);
263
- if (carried) {
264
- const { usage: _usage, metadata: _metadata, message: _message, offset: _offset, ...facts } = carried;
265
- return Object.freeze({ ...facts, usage, metadata });
266
- }
267
- const starts = event.type === "text_start" || event.type === "thinking_start" || event.type === "toolcall_start";
268
- const source = event.type === "toolcall_end" ? event.toolCall : message.content[index];
269
- // A start announced before its block is appended has no seed to freeze yet.
270
- if (starts && source === undefined)
271
- return undefined;
272
- const block = freezeCapture(publicBlock(source));
273
- if (!starts)
274
- return Object.freeze({ usage, metadata, block, ...own });
275
- const rawArgumentPrefix = hint.rawArgumentPrefix ?? "";
276
- const coveredCodeUnits = block.type === "text"
277
- ? block.text.length
278
- : block.type === "thinking"
279
- ? block.thinking.length
280
- : rawArgumentPrefix.length;
281
- const parsedThrough = hint.parsedThrough ?? 0;
282
- state.positions.set(index, { offset: hint.offset ?? coveredCodeUnits, parsedThrough });
283
- return Object.freeze({
284
- usage,
285
- metadata,
286
- block,
287
- coveredCodeUnits,
288
- ...own,
289
- ...(block.type === "toolCall" ? { rawArgumentPrefix, parsedThrough } : {}),
290
- });
291
- }
292
- function usageCopy(state, usage) {
293
- if (state.usage !== undefined && sameValue(usage, state.usage))
294
- return state.usage;
295
- state.usage = frozenUsage(usage);
296
- return state.usage;
297
- }
298
- function frozenUsage(usage) {
299
- return Object.freeze({
300
- ...usage,
301
- cost: Object.freeze({ ...usage.cost }),
302
- ...(usage.contextUsage ? { contextUsage: Object.freeze({ ...usage.contextUsage }) } : {}),
303
- });
304
- }
305
- /** Scalar message fields changed since the last non-delta capture; the baseline moves only once its copy exists. */
306
- function changedMetadata(state, message) {
307
- const changed = {};
308
- for (const key of Object.keys(message)) {
309
- if (key === "content" || key === "usage")
310
- continue;
311
- const value = Reflect.get(message, key);
312
- if (!state.metadata.has(key) || state.metadata.get(key) !== value)
313
- changed[key] = value;
314
- }
315
- for (const key of state.metadata.keys())
316
- if (!(key in message))
317
- changed[key] = null;
318
- const frozen = Object.keys(changed).length === 0 ? NO_CHANGE : freezeCapture(changed);
319
- for (const key of Object.keys(changed)) {
320
- if (key in message)
321
- state.metadata.set(key, Reflect.get(message, key));
322
- else
323
- state.metadata.delete(key);
324
- }
325
- return frozen;
326
- }
327
170
  //# sourceMappingURL=assistant-capture.js.map
@@ -0,0 +1,7 @@
1
+ export declare class SeedFrontierError extends Error {
2
+ readonly code: "missing_next_offset" | "invalid_next_offset";
3
+ constructor(nextOffset: number | undefined);
4
+ }
5
+ /** No inference at a consumer: only full replay or full continuation is supported. */
6
+ export declare function seedNextOffset(nextOffset: number | undefined, seedLength: number): number;
7
+ //# sourceMappingURL=assistant-seed-frontier.d.ts.map
@@ -0,0 +1,14 @@
1
+ export class SeedFrontierError extends Error {
2
+ constructor(nextOffset) {
3
+ super("Block seed requires nextOffset equal to zero or its full UTF-16 length");
4
+ this.name = "SeedFrontierError";
5
+ this.code = nextOffset === undefined ? "missing_next_offset" : "invalid_next_offset";
6
+ }
7
+ }
8
+ /** No inference at a consumer: only full replay or full continuation is supported. */
9
+ export function seedNextOffset(nextOffset, seedLength) {
10
+ if (nextOffset === undefined || !Number.isSafeInteger(nextOffset) || (nextOffset !== 0 && nextOffset !== seedLength))
11
+ throw new SeedFrontierError(nextOffset);
12
+ return nextOffset;
13
+ }
14
+ //# sourceMappingURL=assistant-seed-frontier.js.map
@@ -40,6 +40,7 @@ export declare class AssistantMessageEventStream extends EventStream<AssistantMe
40
40
  });
41
41
  push(event: AssistantMessageEvent, hint?: CaptureHint): void;
42
42
  [Symbol.asyncIterator](): AsyncIterator<AssistantMessageEvent>;
43
+ end(result?: AssistantMessage): void;
43
44
  }
44
45
  /** Factory function for AssistantMessageEventStream (for use in extensions) */
45
46
  export declare function createAssistantMessageEventStream(): AssistantMessageEventStream;
@@ -1,4 +1,5 @@
1
1
  import { captureAssistantEvent, dropAssistantCapture, enableAssistantCapture, setAssistantProgressWriter, setAssistantReader, } from "./assistant-capture.js";
2
+ import { sanitizeProviderErrorMessage } from "./provider-error.js";
2
3
  class FifoQueue {
3
4
  constructor() {
4
5
  this.incoming = [];
@@ -188,6 +189,13 @@ export class AssistantMessageEventStream extends EventStream {
188
189
  push(event, hint) {
189
190
  if (this.done)
190
191
  return;
192
+ // Classify raw provider errors and redact before capture or publication (senpi#4282).
193
+ if ("partial" in event)
194
+ sanitizeProviderErrorMessage(event.partial);
195
+ if (event.type === "error")
196
+ sanitizeProviderErrorMessage(event.error);
197
+ if (event.type === "done")
198
+ sanitizeProviderErrorMessage(event.message);
191
199
  captureAssistantEvent(this, event, hint);
192
200
  super.push(event);
193
201
  }
@@ -200,6 +208,11 @@ export class AssistantMessageEventStream extends EventStream {
200
208
  return { value: entry.event, done: false };
201
209
  });
202
210
  }
211
+ end(result) {
212
+ if (result !== undefined)
213
+ sanitizeProviderErrorMessage(result);
214
+ super.end(result);
215
+ }
203
216
  }
204
217
  /** Factory function for AssistantMessageEventStream (for use in extensions) */
205
218
  export function createAssistantMessageEventStream() {
@@ -0,0 +1,12 @@
1
+ import type { AssistantMessage } from "../types.ts";
2
+ import { redactCredentialShapes, redactSecrets } from "./secret-redaction.ts";
3
+ export { redactCredentialShapes, redactSecrets };
4
+ /** True when provider text says the route does not serve the configured model id (senpi#4282). */
5
+ export declare function isModelConfigError(errorMessage: string | undefined): boolean;
6
+ /**
7
+ * The single point where a provider error body enters a message: classify a model configuration error on the RAW
8
+ * text first (carried as `errorCause`), then keep only the redacted text for every provider error. Idempotent;
9
+ * called for every message a provider stream publishes, so no partial, event or final result carries a raw body.
10
+ */
11
+ export declare function sanitizeProviderErrorMessage(message: AssistantMessage): void;
12
+ //# sourceMappingURL=provider-error.d.ts.map
@@ -0,0 +1,28 @@
1
+ import { redactCredentialShapes, redactSecrets } from "./secret-redaction.js";
2
+ export { redactCredentialShapes, redactSecrets };
3
+ // A route that does not serve a configured model id: the OpenAI-compatible `model_not_found` code and OpenAI's
4
+ // "The model `<id>` does not exist" prose, a gateway's "unknown provider for model", Anthropic's not_found_error whose
5
+ // message names the model ("model: <id>"; any other not_found_error is not one), and Google's "models/<id> is not found".
6
+ // Every quantifier is bounded, so a long run never makes a match attempt expensive.
7
+ const MODEL_CONFIG_ERROR = /\bmodel_not_found\b|\bunknown provider for model\b|\bthe model\s{1,8}[`'"]?[\w.:/-]{1,256}[`'"]?\s{1,8}does not exist\b|\bnot_found_error\b[^}]{0,120}?\bmodel:\s{0,8}[\w.:/-]|\bmodels\/[\w.:-]{1,256} is not found\b/i;
8
+ /** True when provider text says the route does not serve the configured model id (senpi#4282). */
9
+ export function isModelConfigError(errorMessage) {
10
+ return errorMessage !== undefined && MODEL_CONFIG_ERROR.test(errorMessage);
11
+ }
12
+ /**
13
+ * The single point where a provider error body enters a message: classify a model configuration error on the RAW
14
+ * text first (carried as `errorCause`), then keep only the redacted text for every provider error. Idempotent;
15
+ * called for every message a provider stream publishes, so no partial, event or final result carries a raw body.
16
+ */
17
+ export function sanitizeProviderErrorMessage(message) {
18
+ const raw = message.errorMessage;
19
+ if (raw === undefined || raw.length === 0)
20
+ return;
21
+ if (message.stopReason === "error" && message.errorCause?.type !== "model_config" && isModelConfigError(raw)) {
22
+ message.errorCause = { type: "model_config", model: `${message.provider}/${message.model}` };
23
+ }
24
+ const redacted = redactSecrets(raw);
25
+ if (redacted !== raw)
26
+ message.errorMessage = redacted;
27
+ }
28
+ //# sourceMappingURL=provider-error.js.map
@@ -389,6 +389,9 @@ export async function retryAssistantCall(produce, policy, signal, callbacks) {
389
389
  * before restarting the assistant turn.
390
390
  */
391
391
  export function isRetryableAssistantError(message) {
392
+ // A route that does not serve the model answers the same on every attempt, whatever status wraps it.
393
+ if (message.errorCause?.type === "model_config")
394
+ return false;
392
395
  if (message.stopReason === "error" &&
393
396
  message.diagnostics?.some((diagnostic) => diagnostic.type === OAUTH_REFRESH_UNAVAILABLE_DIAGNOSTIC)) {
394
397
  return true;
@@ -0,0 +1,28 @@
1
+ /** Names whose whole value is a credential at any length; every other credential name needs a 16-character value. */
2
+ export declare const STRICT_WORDS: Set<string>;
3
+ export declare const COOKIE_WORDS: Set<string>;
4
+ export declare const SCHEMES: Set<string>;
5
+ export declare const COOKIE_ATTRIBUTES: Set<string>;
6
+ /**
7
+ * The credential word a name ends with, after its normalization: numbered slots (OPENAI_API_KEY_2, token2,
8
+ * x-api-key-1) and a trailing qualifier (api_key_value, apiKeyId) still end with the word. "session" counts only as
9
+ * the whole name, so session_id stays readable; a word elsewhere in a name (token_type, primary_key_column) or inside
10
+ * a word (monkey, tokenizer, max_tokens) does not count.
11
+ */
12
+ export declare function secretWord(text: string, start: number, end: number): string | undefined;
13
+ /** Where the name ends a token: its last run of name characters (a URL path or escape before it is not the name). */
14
+ export declare function nameStart(text: string, start: number, end: number): number;
15
+ export declare function lowerToken(text: string, start: number, end: number, maxLength: number): string | undefined;
16
+ export declare function isInert(text: string, start: number, end: number): boolean;
17
+ /** A known key ends at its alphabet's boundary, not at the end of a URL or filename containing it. */
18
+ export declare function credentialShapeEnd(text: string, start: number, end: number): number;
19
+ /**
20
+ * Where the first credential-shaped run in [start, end) begins, or -1: a known key prefix or a JWT at the start of a
21
+ * word anywhere in the token (after "/", "+", "~", "-", "." or "%", so URL userinfo, paths and joined values count),
22
+ * including percent-encoded token punctuation, never inside a word ("tasks-..." is not "sk-...").
23
+ * After a credential has already been found, withinCredential also checks inside words: an adjoining key may have
24
+ * had its prefix consumed by the first key's alphabet. The caller retains the whole token in that ambiguous case.
25
+ * Constant work per character plus the JWT walks bounded above.
26
+ */
27
+ export declare function credentialShapeStart(text: string, start: number, end: number, withinCredential?: boolean): number;
28
+ //# sourceMappingURL=secret-classifier.d.ts.map