@sayknow-cli/ai 0.3.12 → 0.3.13

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 (190) hide show
  1. package/package.json +24 -25
  2. package/src/providers/amazon-bedrock.ts +37 -33
  3. package/src/providers/anthropic.ts +54 -49
  4. package/src/providers/aws-credential-config.ts +180 -0
  5. package/src/providers/aws-credentials.ts +20 -63
  6. package/src/providers/google-gemini-cli.ts +27 -3
  7. package/src/providers/google-shared.ts +55 -3
  8. package/src/types.ts +2 -0
  9. package/dist/types/api-registry.d.ts +0 -30
  10. package/dist/types/auth-broker/client.d.ts +0 -67
  11. package/dist/types/auth-broker/index.d.ts +0 -5
  12. package/dist/types/auth-broker/refresher.d.ts +0 -25
  13. package/dist/types/auth-broker/remote-store.d.ts +0 -99
  14. package/dist/types/auth-broker/server.d.ts +0 -32
  15. package/dist/types/auth-broker/types.d.ts +0 -110
  16. package/dist/types/auth-broker/wire-schemas.d.ts +0 -443
  17. package/dist/types/auth-gateway/http.d.ts +0 -40
  18. package/dist/types/auth-gateway/index.d.ts +0 -3
  19. package/dist/types/auth-gateway/server.d.ts +0 -17
  20. package/dist/types/auth-gateway/types.d.ts +0 -115
  21. package/dist/types/auth-storage.d.ts +0 -695
  22. package/dist/types/cli.d.ts +0 -2
  23. package/dist/types/index.d.ts +0 -51
  24. package/dist/types/model-cache.d.ts +0 -17
  25. package/dist/types/model-manager.d.ts +0 -62
  26. package/dist/types/model-retirements.d.ts +0 -6
  27. package/dist/types/model-thinking.d.ts +0 -74
  28. package/dist/types/models.d.ts +0 -21
  29. package/dist/types/provider-details.d.ts +0 -24
  30. package/dist/types/provider-models/bundled-references.d.ts +0 -4
  31. package/dist/types/provider-models/descriptors.d.ts +0 -48
  32. package/dist/types/provider-models/google.d.ts +0 -20
  33. package/dist/types/provider-models/index.d.ts +0 -5
  34. package/dist/types/provider-models/ollama.d.ts +0 -7
  35. package/dist/types/provider-models/openai-compat.d.ts +0 -256
  36. package/dist/types/provider-models/special.d.ts +0 -19
  37. package/dist/types/providers/amazon-bedrock.d.ts +0 -60
  38. package/dist/types/providers/anthropic-messages-server-schema.d.ts +0 -450
  39. package/dist/types/providers/anthropic-messages-server.d.ts +0 -17
  40. package/dist/types/providers/anthropic.d.ts +0 -208
  41. package/dist/types/providers/aws-credentials.d.ts +0 -43
  42. package/dist/types/providers/aws-eventstream.d.ts +0 -38
  43. package/dist/types/providers/aws-sigv4.d.ts +0 -55
  44. package/dist/types/providers/azure-openai-responses.d.ts +0 -15
  45. package/dist/types/providers/composer-discipline.d.ts +0 -26
  46. package/dist/types/providers/cursor/client-version.d.ts +0 -10
  47. package/dist/types/providers/cursor/gen/agent_pb.d.ts +0 -13022
  48. package/dist/types/providers/cursor.d.ts +0 -45
  49. package/dist/types/providers/error-message.d.ts +0 -27
  50. package/dist/types/providers/github-copilot-headers.d.ts +0 -40
  51. package/dist/types/providers/gitlab-duo.d.ts +0 -27
  52. package/dist/types/providers/google-auth.d.ts +0 -24
  53. package/dist/types/providers/google-gemini-cli.d.ts +0 -75
  54. package/dist/types/providers/google-gemini-headers.d.ts +0 -43
  55. package/dist/types/providers/google-shared.d.ts +0 -173
  56. package/dist/types/providers/google-types.d.ts +0 -138
  57. package/dist/types/providers/google-vertex.d.ts +0 -7
  58. package/dist/types/providers/google.d.ts +0 -4
  59. package/dist/types/providers/grammar.d.ts +0 -1
  60. package/dist/types/providers/kimi.d.ts +0 -27
  61. package/dist/types/providers/mock.d.ts +0 -177
  62. package/dist/types/providers/ollama.d.ts +0 -41
  63. package/dist/types/providers/openai-anthropic-shim.d.ts +0 -31
  64. package/dist/types/providers/openai-bounded-rate-limits.d.ts +0 -3
  65. package/dist/types/providers/openai-chat-server-schema.d.ts +0 -815
  66. package/dist/types/providers/openai-chat-server.d.ts +0 -16
  67. package/dist/types/providers/openai-codex/constants.d.ts +0 -26
  68. package/dist/types/providers/openai-codex/request-transformer.d.ts +0 -50
  69. package/dist/types/providers/openai-codex/response-handler.d.ts +0 -17
  70. package/dist/types/providers/openai-codex-responses.d.ts +0 -67
  71. package/dist/types/providers/openai-completions-compat.d.ts +0 -27
  72. package/dist/types/providers/openai-completions.d.ts +0 -33
  73. package/dist/types/providers/openai-request-transform.d.ts +0 -4
  74. package/dist/types/providers/openai-responses-server-schema.d.ts +0 -392
  75. package/dist/types/providers/openai-responses-server.d.ts +0 -17
  76. package/dist/types/providers/openai-responses-shared.d.ts +0 -104
  77. package/dist/types/providers/openai-responses.d.ts +0 -32
  78. package/dist/types/providers/pi-native-client.d.ts +0 -13
  79. package/dist/types/providers/pi-native-server.d.ts +0 -68
  80. package/dist/types/providers/register-builtins.d.ts +0 -31
  81. package/dist/types/providers/synthetic.d.ts +0 -26
  82. package/dist/types/providers/transform-messages.d.ts +0 -14
  83. package/dist/types/providers/vision-guard.d.ts +0 -8
  84. package/dist/types/rate-limit-utils.d.ts +0 -19
  85. package/dist/types/stream.d.ts +0 -43
  86. package/dist/types/types.d.ts +0 -829
  87. package/dist/types/usage/claude.d.ts +0 -3
  88. package/dist/types/usage/gemini.d.ts +0 -2
  89. package/dist/types/usage/github-copilot.d.ts +0 -7
  90. package/dist/types/usage/google-antigravity.d.ts +0 -2
  91. package/dist/types/usage/grok-cli.d.ts +0 -10
  92. package/dist/types/usage/kimi.d.ts +0 -2
  93. package/dist/types/usage/minimax-code.d.ts +0 -2
  94. package/dist/types/usage/openai-codex.d.ts +0 -3
  95. package/dist/types/usage/shared.d.ts +0 -1
  96. package/dist/types/usage/zai.d.ts +0 -2
  97. package/dist/types/usage.d.ts +0 -258
  98. package/dist/types/utils/abort.d.ts +0 -19
  99. package/dist/types/utils/anthropic-auth.d.ts +0 -31
  100. package/dist/types/utils/discovery/antigravity.d.ts +0 -67
  101. package/dist/types/utils/discovery/codex.d.ts +0 -38
  102. package/dist/types/utils/discovery/cursor.d.ts +0 -23
  103. package/dist/types/utils/discovery/gemini.d.ts +0 -25
  104. package/dist/types/utils/discovery/index.d.ts +0 -4
  105. package/dist/types/utils/discovery/openai-compatible.d.ts +0 -74
  106. package/dist/types/utils/event-stream.d.ts +0 -33
  107. package/dist/types/utils/fireworks-model-id.d.ts +0 -10
  108. package/dist/types/utils/foundry.d.ts +0 -1
  109. package/dist/types/utils/h2-fetch.d.ts +0 -22
  110. package/dist/types/utils/http-inspector.d.ts +0 -35
  111. package/dist/types/utils/idle-iterator.d.ts +0 -67
  112. package/dist/types/utils/json-parse.d.ts +0 -18
  113. package/dist/types/utils/oauth/alibaba-coding-plan.d.ts +0 -18
  114. package/dist/types/utils/oauth/anthropic.d.ts +0 -22
  115. package/dist/types/utils/oauth/api-key-login.d.ts +0 -35
  116. package/dist/types/utils/oauth/api-key-validation.d.ts +0 -27
  117. package/dist/types/utils/oauth/callback-server.d.ts +0 -60
  118. package/dist/types/utils/oauth/cerebras.d.ts +0 -1
  119. package/dist/types/utils/oauth/cloudflare-ai-gateway.d.ts +0 -18
  120. package/dist/types/utils/oauth/cursor.d.ts +0 -15
  121. package/dist/types/utils/oauth/deepinfra.d.ts +0 -1
  122. package/dist/types/utils/oauth/deepseek.d.ts +0 -10
  123. package/dist/types/utils/oauth/firepass.d.ts +0 -1
  124. package/dist/types/utils/oauth/fireworks.d.ts +0 -1
  125. package/dist/types/utils/oauth/fugu.d.ts +0 -1
  126. package/dist/types/utils/oauth/github-copilot.d.ts +0 -38
  127. package/dist/types/utils/oauth/gitlab-duo.d.ts +0 -3
  128. package/dist/types/utils/oauth/glm-zcode.d.ts +0 -71
  129. package/dist/types/utils/oauth/google-antigravity.d.ts +0 -11
  130. package/dist/types/utils/oauth/google-gemini-cli.d.ts +0 -10
  131. package/dist/types/utils/oauth/google-oauth-shared.d.ts +0 -28
  132. package/dist/types/utils/oauth/huggingface.d.ts +0 -19
  133. package/dist/types/utils/oauth/index.d.ts +0 -39
  134. package/dist/types/utils/oauth/kagi.d.ts +0 -17
  135. package/dist/types/utils/oauth/kilo.d.ts +0 -5
  136. package/dist/types/utils/oauth/kimi.d.ts +0 -21
  137. package/dist/types/utils/oauth/litellm.d.ts +0 -18
  138. package/dist/types/utils/oauth/lm-studio.d.ts +0 -17
  139. package/dist/types/utils/oauth/minimax-code.d.ts +0 -28
  140. package/dist/types/utils/oauth/moonshot.d.ts +0 -1
  141. package/dist/types/utils/oauth/nanogpt.d.ts +0 -1
  142. package/dist/types/utils/oauth/nvidia.d.ts +0 -18
  143. package/dist/types/utils/oauth/ollama-cloud.d.ts +0 -2
  144. package/dist/types/utils/oauth/ollama.d.ts +0 -18
  145. package/dist/types/utils/oauth/openai-codex.d.ts +0 -21
  146. package/dist/types/utils/oauth/opencode.d.ts +0 -18
  147. package/dist/types/utils/oauth/parallel.d.ts +0 -17
  148. package/dist/types/utils/oauth/perplexity.d.ts +0 -9
  149. package/dist/types/utils/oauth/pkce.d.ts +0 -8
  150. package/dist/types/utils/oauth/qianfan.d.ts +0 -17
  151. package/dist/types/utils/oauth/qwen-portal.d.ts +0 -19
  152. package/dist/types/utils/oauth/synthetic.d.ts +0 -1
  153. package/dist/types/utils/oauth/tavily.d.ts +0 -17
  154. package/dist/types/utils/oauth/together.d.ts +0 -1
  155. package/dist/types/utils/oauth/types.d.ts +0 -45
  156. package/dist/types/utils/oauth/venice.d.ts +0 -18
  157. package/dist/types/utils/oauth/vercel-ai-gateway.d.ts +0 -18
  158. package/dist/types/utils/oauth/vllm.d.ts +0 -16
  159. package/dist/types/utils/oauth/xai.d.ts +0 -30
  160. package/dist/types/utils/oauth/xiaomi.d.ts +0 -25
  161. package/dist/types/utils/oauth/zai.d.ts +0 -18
  162. package/dist/types/utils/oauth/zenmux.d.ts +0 -1
  163. package/dist/types/utils/overflow.d.ts +0 -64
  164. package/dist/types/utils/parse-bind.d.ts +0 -23
  165. package/dist/types/utils/provider-response.d.ts +0 -3
  166. package/dist/types/utils/retry-after.d.ts +0 -3
  167. package/dist/types/utils/retry-budget.d.ts +0 -1
  168. package/dist/types/utils/retry.d.ts +0 -26
  169. package/dist/types/utils/schema/adapt.d.ts +0 -24
  170. package/dist/types/utils/schema/compatibility.d.ts +0 -30
  171. package/dist/types/utils/schema/dereference.d.ts +0 -11
  172. package/dist/types/utils/schema/draft.d.ts +0 -10
  173. package/dist/types/utils/schema/equality.d.ts +0 -4
  174. package/dist/types/utils/schema/fields.d.ts +0 -49
  175. package/dist/types/utils/schema/index.d.ts +0 -14
  176. package/dist/types/utils/schema/json-schema-validator.d.ts +0 -12
  177. package/dist/types/utils/schema/meta-validator.d.ts +0 -2
  178. package/dist/types/utils/schema/normalize.d.ts +0 -93
  179. package/dist/types/utils/schema/root-combinator.d.ts +0 -12
  180. package/dist/types/utils/schema/spill.d.ts +0 -8
  181. package/dist/types/utils/schema/stamps.d.ts +0 -25
  182. package/dist/types/utils/schema/types.d.ts +0 -4
  183. package/dist/types/utils/schema/wire.d.ts +0 -54
  184. package/dist/types/utils/schema/zod-decontaminate.d.ts +0 -31
  185. package/dist/types/utils/sse-debug.d.ts +0 -10
  186. package/dist/types/utils/tool-call-healing.d.ts +0 -71
  187. package/dist/types/utils/tool-choice-capability.d.ts +0 -41
  188. package/dist/types/utils/tool-choice.d.ts +0 -50
  189. package/dist/types/utils/validation.d.ts +0 -17
  190. package/dist/types/utils.d.ts +0 -35
@@ -23,6 +23,13 @@ import * as fs from "node:fs";
23
23
  import * as os from "node:os";
24
24
  import * as path from "node:path";
25
25
  import { $env, isEnoent, logger } from "@sayknow-cli/utils";
26
+ import {
27
+ type AwsIniFile,
28
+ classifyAwsProfileCapability,
29
+ parseAwsIni,
30
+ readAwsStaticEnvironmentCredentials,
31
+ resolveAwsCredentialSource,
32
+ } from "./aws-credential-config";
26
33
  import type { AwsCredentials } from "./aws-sigv4";
27
34
 
28
35
  export interface ResolvedCredentials extends AwsCredentials {
@@ -48,7 +55,7 @@ interface CacheEntry {
48
55
  const cache: Map<string, CacheEntry> = new Map();
49
56
 
50
57
  export async function resolveAwsCredentials(opts: CredentialResolveOptions = {}): Promise<ResolvedCredentials> {
51
- const profile = opts.profile || $env.AWS_PROFILE || "default";
58
+ const profile = resolveAwsCredentialSource({ profile: opts.profile }).profile;
52
59
  const region = opts.region || $env.AWS_REGION || $env.AWS_DEFAULT_REGION || "us-east-1";
53
60
  const cacheKey = `${profile}\x00${region}`;
54
61
 
@@ -62,7 +69,7 @@ export async function resolveAwsCredentials(opts: CredentialResolveOptions = {})
62
69
 
63
70
  async function resolveFresh(profile: string, region: string, signal?: AbortSignal): Promise<ResolvedCredentials> {
64
71
  // 1. Environment first — matches the AWS SDK chain order.
65
- const envCreds = readEnvCredentials();
72
+ const envCreds = readAwsStaticEnvironmentCredentials();
66
73
  if (envCreds) return envCreds;
67
74
 
68
75
  // 2. Profile (static or SSO).
@@ -81,52 +88,10 @@ async function resolveFresh(profile: string, region: string, signal?: AbortSigna
81
88
  );
82
89
  }
83
90
 
84
- function readEnvCredentials(): ResolvedCredentials | undefined {
85
- const ak = $env.AWS_ACCESS_KEY_ID;
86
- const sk = $env.AWS_SECRET_ACCESS_KEY;
87
- if (!ak || !sk) return undefined;
88
- const token = $env.AWS_SESSION_TOKEN;
89
- return token
90
- ? { accessKeyId: ak, secretAccessKey: sk, sessionToken: token }
91
- : { accessKeyId: ak, secretAccessKey: sk };
92
- }
93
-
94
- // ---------- INI parsing ----------
95
-
96
- /** Map of section name -> map of key -> value. Section names are stripped of
97
- * any leading `profile ` (so `~/.aws/config` aligns with `~/.aws/credentials`). */
98
- type IniFile = Record<string, Record<string, string>>;
99
-
100
- function parseIni(text: string): IniFile {
101
- const out: IniFile = {};
102
- let current: Record<string, string> | null = null;
103
- for (const rawLine of text.split(/\r?\n/)) {
104
- const line = rawLine.trim();
105
- if (!line || line.startsWith("#") || line.startsWith(";")) continue;
106
- if (line.startsWith("[") && line.endsWith("]")) {
107
- let name = line.slice(1, -1).trim();
108
- if (name.startsWith("profile ")) name = name.slice(8).trim();
109
- if (name.startsWith("sso-session ")) name = `sso-session:${name.slice(12).trim()}`;
110
- let section = out[name];
111
- if (!section) {
112
- section = {};
113
- out[name] = section;
114
- }
115
- current = section;
116
- continue;
117
- }
118
- if (!current) continue;
119
- const eq = line.indexOf("=");
120
- if (eq === -1) continue;
121
- current[line.slice(0, eq).trim()] = line.slice(eq + 1).trim();
122
- }
123
- return out;
124
- }
125
-
126
- async function readIniFile(p: string): Promise<IniFile | undefined> {
91
+ async function readIniFile(p: string): Promise<AwsIniFile | undefined> {
127
92
  try {
128
93
  const text = await fs.promises.readFile(p, "utf8");
129
- return parseIni(text);
94
+ return parseAwsIni(text);
130
95
  } catch (err) {
131
96
  if (isEnoent(err)) return undefined;
132
97
  throw err;
@@ -140,9 +105,7 @@ async function readProfileCredentials(
140
105
  region: string,
141
106
  signal: AbortSignal | undefined,
142
107
  ): Promise<ResolvedCredentials | undefined> {
143
- const home = os.homedir();
144
- const credentialsPath = $env.AWS_SHARED_CREDENTIALS_FILE || path.join(home, ".aws", "credentials");
145
- const configPath = $env.AWS_CONFIG_FILE || path.join(home, ".aws", "config");
108
+ const { credentialsPath, configPath } = resolveAwsCredentialSource({ profile });
146
109
 
147
110
  const credentialsIni = await readIniFile(credentialsPath);
148
111
  const configIni = await readIniFile(configPath);
@@ -152,22 +115,16 @@ async function readProfileCredentials(
152
115
  const merged: Record<string, string> = { ...(configIni?.[profile] ?? {}), ...(credentialsIni?.[profile] ?? {}) };
153
116
  if (Object.keys(merged).length === 0) return undefined;
154
117
 
155
- if (merged.aws_access_key_id && merged.aws_secret_access_key) {
156
- const out: ResolvedCredentials = {
157
- accessKeyId: merged.aws_access_key_id,
158
- secretAccessKey: merged.aws_secret_access_key,
159
- };
118
+ const capability = classifyAwsProfileCapability(profile, credentialsIni, configIni);
119
+ if (capability === "static") {
120
+ const { aws_access_key_id: accessKeyId, aws_secret_access_key: secretAccessKey } = merged;
121
+ if (!accessKeyId || !secretAccessKey) return undefined;
122
+ const out: ResolvedCredentials = { accessKeyId, secretAccessKey };
160
123
  if (merged.aws_session_token) out.sessionToken = merged.aws_session_token;
161
124
  return out;
162
125
  }
163
-
164
- if (merged.sso_account_id && merged.sso_role_name) {
165
- return readSsoCredentials(merged, configIni, region, signal);
166
- }
167
-
168
- if (merged.credential_process) {
169
- return readCredentialProcess(profile, merged.credential_process, signal);
170
- }
126
+ if (capability === "sso") return readSsoCredentials(merged, configIni, region, signal);
127
+ if (capability === "process") return readCredentialProcess(profile, merged.credential_process, signal);
171
128
 
172
129
  return undefined;
173
130
  }
@@ -181,7 +138,7 @@ interface SsoCachedToken {
181
138
 
182
139
  async function readSsoCredentials(
183
140
  profileCfg: Record<string, string>,
184
- configIni: IniFile | undefined,
141
+ configIni: AwsIniFile | undefined,
185
142
  defaultRegion: string,
186
143
  signal: AbortSignal | undefined,
187
144
  ): Promise<ResolvedCredentials | undefined> {
@@ -40,10 +40,14 @@ import {
40
40
  convertMessages,
41
41
  convertTools,
42
42
  type GoogleThinkingLevel,
43
+ getGooglePromptBlockReason,
44
+ isGoogleCandidateSafetyStopReason,
45
+ isGooglePromptSafetyStopReason,
43
46
  isThinkingPart,
44
47
  mapStopReasonString,
45
48
  mapToolChoice,
46
49
  nextToolCallId,
50
+ PROVIDER_SAFETY_STOP,
47
51
  pushBlockEndEvent,
48
52
  pushToolCallEvents,
49
53
  retainThoughtSignature,
@@ -255,6 +259,9 @@ interface CloudCodeAssistResponseChunk {
255
259
  };
256
260
  finishReason?: string;
257
261
  }>;
262
+ promptFeedback?: {
263
+ blockReason?: string;
264
+ };
258
265
  usageMetadata?: {
259
266
  promptTokenCount?: number;
260
267
  candidatesTokenCount?: number;
@@ -537,9 +544,26 @@ export const streamGoogleGeminiCli: StreamFunction<"google-gemini-cli"> = (
537
544
  }
538
545
 
539
546
  if (candidate?.finishReason) {
540
- output.stopReason = mapStopReasonString(candidate.finishReason);
541
- if (output.content.some(b => b.type === "toolCall")) {
542
- output.stopReason = "toolUse";
547
+ if (isGoogleCandidateSafetyStopReason(candidate.finishReason)) {
548
+ hasContent = true;
549
+ output.errorKind = PROVIDER_SAFETY_STOP;
550
+ output.stopReason = "error";
551
+ } else if (output.errorKind !== PROVIDER_SAFETY_STOP) {
552
+ output.stopReason = mapStopReasonString(candidate.finishReason);
553
+ if (output.stopReason === "stop" && output.content.some(b => b.type === "toolCall")) {
554
+ output.stopReason = "toolUse";
555
+ }
556
+ }
557
+ }
558
+
559
+ const blockReason = getGooglePromptBlockReason(responseData.promptFeedback);
560
+ if (blockReason) {
561
+ hasContent = true;
562
+ if (isGooglePromptSafetyStopReason(blockReason)) {
563
+ output.errorKind = PROVIDER_SAFETY_STOP;
564
+ output.stopReason = "error";
565
+ } else if (output.errorKind !== PROVIDER_SAFETY_STOP) {
566
+ output.stopReason = "error";
543
567
  }
544
568
  }
545
569
 
@@ -51,6 +51,43 @@ export type {
51
51
  export { normalizeSchemaForGoogle };
52
52
 
53
53
  type GoogleApiType = "google-generative-ai" | "google-gemini-cli" | "google-vertex";
54
+ export const PROVIDER_SAFETY_STOP = "provider_safety_stop";
55
+
56
+ export function isGoogleCandidateSafetyStopReason(reason: string): boolean {
57
+ switch (reason) {
58
+ case "SAFETY":
59
+ case "IMAGE_SAFETY":
60
+ case "PROHIBITED_CONTENT":
61
+ case "IMAGE_PROHIBITED_CONTENT":
62
+ case "SPII":
63
+ case "BLOCKLIST":
64
+ case "RECITATION":
65
+ case "IMAGE_RECITATION":
66
+ case "MODEL_ARMOR":
67
+ return true;
68
+ default:
69
+ return false;
70
+ }
71
+ }
72
+
73
+ export function isGooglePromptSafetyStopReason(reason: string): boolean {
74
+ switch (reason) {
75
+ case "SAFETY":
76
+ case "IMAGE_SAFETY":
77
+ case "PROHIBITED_CONTENT":
78
+ case "BLOCKLIST":
79
+ case "MODEL_ARMOR":
80
+ case "JAILBREAK":
81
+ return true;
82
+ default:
83
+ return false;
84
+ }
85
+ }
86
+
87
+ export function getGooglePromptBlockReason(promptFeedback: { blockReason?: unknown } | undefined): string | undefined {
88
+ const blockReason = promptFeedback?.blockReason;
89
+ return typeof blockReason === "string" && blockReason.length > 0 ? blockReason : undefined;
90
+ }
54
91
 
55
92
  /**
56
93
  * Thinking level for Gemini 3 models. Mirrors Google's `ThinkingLevel` enum values.
@@ -607,9 +644,24 @@ export async function consumeGoogleStream<T extends GoogleApiType>(args: {
607
644
  }
608
645
 
609
646
  if (candidate?.finishReason) {
610
- output.stopReason = mapStopReason(candidate.finishReason);
611
- if (output.content.some(b => b.type === "toolCall")) {
612
- output.stopReason = "toolUse";
647
+ if (isGoogleCandidateSafetyStopReason(candidate.finishReason)) {
648
+ output.errorKind = PROVIDER_SAFETY_STOP;
649
+ output.stopReason = "error";
650
+ } else if (output.errorKind !== PROVIDER_SAFETY_STOP) {
651
+ output.stopReason = mapStopReason(candidate.finishReason);
652
+ if (output.stopReason === "stop" && output.content.some(b => b.type === "toolCall")) {
653
+ output.stopReason = "toolUse";
654
+ }
655
+ }
656
+ }
657
+
658
+ const blockReason = getGooglePromptBlockReason(chunk.promptFeedback);
659
+ if (blockReason) {
660
+ if (isGooglePromptSafetyStopReason(blockReason)) {
661
+ output.errorKind = PROVIDER_SAFETY_STOP;
662
+ output.stopReason = "error";
663
+ } else if (output.errorKind !== PROVIDER_SAFETY_STOP) {
664
+ output.stopReason = "error";
613
665
  }
614
666
  }
615
667
 
package/src/types.ts CHANGED
@@ -552,6 +552,7 @@ export interface Usage {
552
552
  }
553
553
 
554
554
  export type StopReason = "stop" | "length" | "toolUse" | "error" | "aborted";
555
+ export type AssistantErrorKind = "provider_safety_stop";
555
556
 
556
557
  export interface OpenAIResponsesHistoryPayload {
557
558
  type: "openaiResponsesHistory";
@@ -594,6 +595,7 @@ export interface AssistantMessage {
594
595
  usage: Usage;
595
596
  stopReason: StopReason;
596
597
  errorMessage?: string;
598
+ errorKind?: AssistantErrorKind;
597
599
  /** HTTP status surfaced by the provider when the request failed. Populated by every provider's catch block alongside `errorMessage` so consumers (auth retry, telemetry, UI) can branch without regex-scraping the message. */
598
600
  errorStatus?: number;
599
601
  /**
@@ -1,30 +0,0 @@
1
- /**
2
- * Custom API provider registry.
3
- *
4
- * Allows extensions to register streaming functions for custom API types
5
- * (e.g., "vertex-Anthropic model-api") that are not built into stream.ts.
6
- */
7
- import type { Api, AssistantMessageEventStream, Context, Model, SimpleStreamOptions, StreamOptions } from "./types";
8
- export type CustomStreamFn = (model: Model<Api>, context: Context, options?: StreamOptions) => AssistantMessageEventStream;
9
- export type CustomStreamSimpleFn = (model: Model<Api>, context: Context, options?: SimpleStreamOptions) => AssistantMessageEventStream;
10
- export interface RegisteredCustomApi {
11
- stream: CustomStreamFn;
12
- streamSimple: CustomStreamSimpleFn;
13
- sourceId?: string;
14
- }
15
- /**
16
- * Register a custom API streaming function.
17
- */
18
- export declare function registerCustomApi(api: string, streamSimple: CustomStreamSimpleFn, sourceId?: string, stream?: CustomStreamFn): void;
19
- /**
20
- * Get a custom API provider by API identifier.
21
- */
22
- export declare function getCustomApi(api: string): RegisteredCustomApi | undefined;
23
- /**
24
- * Remove all custom APIs registered by a specific source (e.g., extension path).
25
- */
26
- export declare function unregisterCustomApis(sourceId: string): void;
27
- /**
28
- * Clear all custom API registrations.
29
- */
30
- export declare function clearCustomApis(): void;
@@ -1,67 +0,0 @@
1
- import type { AuthCredential } from "../auth-storage";
2
- import type { CredentialDisableResponse, CredentialIfAbsentUploadResponse, CredentialRefreshResponse, CredentialUploadResponse, HealthzResponse, SnapshotResponse, SnapshotStreamEvent, UsageResponse } from "./types";
3
- export interface AuthBrokerClientOptions {
4
- /** Base URL (e.g. `https://broker.tailnet:8765`). Trailing slashes are trimmed. */
5
- url: string;
6
- /** Bearer token used for everything except `healthz`. */
7
- token: string;
8
- /** Per-request timeout in milliseconds. Default 10s. */
9
- timeoutMs?: number;
10
- /** Retry connection errors this many times. Default 1. */
11
- maxRetries?: number;
12
- /** Override fetch (used in tests). Default global `fetch`. */
13
- fetchImpl?: typeof fetch;
14
- }
15
- export declare class AuthBrokerError extends Error {
16
- readonly status: number | undefined;
17
- readonly body: string | undefined;
18
- constructor(message: string, opts?: {
19
- status?: number;
20
- body?: string;
21
- cause?: unknown;
22
- });
23
- }
24
- /**
25
- * Thrown when a broker responds 404 to `GET /v1/snapshot/stream` — old
26
- * brokers that predate the SSE endpoint. Callers (`RemoteAuthCredentialStore`)
27
- * detect this sentinel to fall back to long-polling permanently.
28
- */
29
- export declare class AuthBrokerStreamUnsupportedError extends AuthBrokerError {
30
- constructor(message?: string);
31
- }
32
- export interface FetchSnapshotOptions {
33
- ifGenerationGt?: number;
34
- waitMs?: number;
35
- signal?: AbortSignal;
36
- }
37
- export type FetchSnapshotResult = {
38
- status: 200;
39
- snapshot: SnapshotResponse;
40
- generation: number;
41
- } | {
42
- status: 304;
43
- generation: number;
44
- };
45
- export declare class AuthBrokerClient {
46
- #private;
47
- constructor(opts: AuthBrokerClientOptions);
48
- healthz(signal?: AbortSignal): Promise<HealthzResponse>;
49
- fetchSnapshot(opts?: FetchSnapshotOptions): Promise<FetchSnapshotResult>;
50
- /**
51
- * Subscribe to the broker's SSE snapshot stream. The first frame is always
52
- * a full `snapshot`; subsequent frames are `entry` upserts / refreshes or
53
- * `removed` deletes. Caller controls lifecycle via `opts.signal`.
54
- *
55
- * Throws {@link AuthBrokerStreamUnsupportedError} when the broker responds
56
- * 404 — older brokers predate this endpoint and the caller should fall back
57
- * to long-polling for the remainder of its lifetime.
58
- */
59
- openSnapshotStream(opts?: {
60
- signal?: AbortSignal;
61
- }): AsyncGenerator<SnapshotStreamEvent>;
62
- fetchUsage(signal?: AbortSignal): Promise<UsageResponse>;
63
- refreshCredential(id: number, signal?: AbortSignal): Promise<CredentialRefreshResponse>;
64
- disableCredential(id: number, cause: string, signal?: AbortSignal): Promise<CredentialDisableResponse>;
65
- uploadCredential(provider: string, credential: AuthCredential, signal?: AbortSignal): Promise<CredentialUploadResponse>;
66
- uploadCredentialIfAbsent(provider: string, credential: AuthCredential, signal?: AbortSignal): Promise<CredentialIfAbsentUploadResponse>;
67
- }
@@ -1,5 +0,0 @@
1
- export * from "./client";
2
- export * from "./refresher";
3
- export * from "./remote-store";
4
- export * from "./server";
5
- export * from "./types";
@@ -1,25 +0,0 @@
1
- import type { AuthStorage } from "../auth-storage";
2
- export interface AuthBrokerRefresherOptions {
3
- storage: AuthStorage;
4
- /** Refresh credentials expiring within this window. Default 5 min. */
5
- refreshSkewMs?: number;
6
- /** Loop cadence. Default 60s. */
7
- refreshIntervalMs?: number;
8
- /** Override clock (tests). */
9
- now?: () => number;
10
- }
11
- export interface AuthBrokerRefresherSchedule {
12
- enabled: boolean;
13
- intervalMs: number;
14
- skewMs: number;
15
- nextSweepAt: number;
16
- }
17
- export declare class AuthBrokerRefresher {
18
- #private;
19
- constructor(opts: AuthBrokerRefresherOptions);
20
- start(): void;
21
- stop(): void;
22
- getSchedule(): AuthBrokerRefresherSchedule;
23
- /** Run one sweep. Exposed for tests. */
24
- tick(): Promise<void>;
25
- }
@@ -1,99 +0,0 @@
1
- import { type AuthCredential, type AuthCredentialIfAbsentResult, type AuthCredentialStore, type OAuthCredential, type StoredAuthCredential } from "../auth-storage";
2
- import type { Provider } from "../types";
3
- import type { UsageReport } from "../usage";
4
- import type { OAuthCredentials } from "../utils/oauth/types";
5
- import { type AuthBrokerClient } from "./client";
6
- import type { SnapshotResponse } from "./types";
7
- export interface RemoteAuthCredentialStoreOptions {
8
- client: AuthBrokerClient;
9
- /**
10
- * Initial snapshot. When omitted, callers must call
11
- * {@link RemoteAuthCredentialStore.refreshSnapshot} before the first read.
12
- */
13
- initialSnapshot?: SnapshotResponse;
14
- /**
15
- * Subscribe to the broker's SSE snapshot stream when available. Falls back
16
- * to long-poll permanently when the broker returns 404. Default `true`.
17
- */
18
- streamSnapshots?: boolean;
19
- }
20
- export declare class RemoteAuthCredentialStore implements AuthCredentialStore {
21
- #private;
22
- constructor(opts: RemoteAuthCredentialStoreOptions);
23
- get client(): AuthBrokerClient;
24
- get snapshot(): SnapshotResponse;
25
- /** Re-hydrate the in-memory snapshot from the broker. */
26
- refreshSnapshot(): Promise<SnapshotResponse>;
27
- listAuthCredentials(provider?: string): StoredAuthCredential[];
28
- /**
29
- * In-memory update from a successful refresh through the broker. AuthStorage
30
- * calls this after `#replaceCredentialAt`; the broker already persisted the
31
- * authoritative row, so we just mirror it.
32
- */
33
- updateAuthCredential(id: number, credential: AuthCredential): void;
34
- deleteAuthCredential(id: number, disabledCause: string): void;
35
- tryDisableAuthCredentialIfMatches(id: number, _expectedData: string, disabledCause: string): boolean;
36
- waitForFreshSnapshot(maxWaitMs: number, opts?: {
37
- signal?: AbortSignal;
38
- }): Promise<boolean>;
39
- prepareForRequest(credentialId: number, opts?: {
40
- signal?: AbortSignal;
41
- }): Promise<boolean>;
42
- markCredentialSuspect(credentialId: number, opts?: {
43
- signal?: AbortSignal;
44
- }): Promise<void>;
45
- replaceAuthCredentialsForProvider(_provider: string, _credentials: AuthCredential[]): StoredAuthCredential[];
46
- upsertAuthCredentialForProvider(_provider: string, _credential: AuthCredential): StoredAuthCredential[];
47
- upsertAuthCredentialForProviderIfAbsent(_provider: string, _credential: AuthCredential): AuthCredentialIfAbsentResult;
48
- deleteAuthCredentialsForProvider(_provider: string, _disabledCause: string): void;
49
- /**
50
- * Upsert a single credential through the broker. The broker server is the
51
- * canonical writer — see `POST /v1/credential`. The redacted snapshot
52
- * entries returned by the server replace the provider's rows in our local
53
- * snapshot, and the global snapshot is then refreshed in the background so
54
- * any concurrent peer (refresh, generation bump) stays in sync.
55
- */
56
- upsertAuthCredentialRemote(provider: string, credential: AuthCredential): Promise<StoredAuthCredential[]>;
57
- upsertAuthCredentialRemoteIfAbsent(provider: string, credential: AuthCredential): Promise<AuthCredentialIfAbsentResult>;
58
- /**
59
- * Replace-all semantics: disable every active credential for the provider,
60
- * then upload each of the new credentials. Used by API-key login so a new
61
- * key clobbers any previously stored key for the same provider.
62
- */
63
- replaceAuthCredentialsRemote(provider: string, credentials: AuthCredential[]): Promise<StoredAuthCredential[]>;
64
- /**
65
- * Logout: disable every active credential for the provider on the broker,
66
- * then drop them from the local snapshot. Refresh fetches the authoritative
67
- * post-state in the background.
68
- */
69
- deleteAuthCredentialsRemote(provider: string, disabledCause: string): Promise<void>;
70
- getCache(key: string): string | null;
71
- setCache(key: string, value: string, expiresAtSec: number): void;
72
- cleanExpiredCache(): void;
73
- deleteCachePrefix(prefix: string): void;
74
- /**
75
- * Store-level hook consumed by `AuthStorage` — routes refresh through the
76
- * broker so the actual refresh token never leaves the broker host. Returns
77
- * the broker-redacted credential with {@link REMOTE_REFRESH_SENTINEL} in
78
- * the `refresh` slot.
79
- */
80
- refreshOAuthCredential(_provider: Provider, credentialId: number, _credential: OAuthCredential, signal?: AbortSignal): Promise<OAuthCredentials>;
81
- /**
82
- * Store-level hook consumed by `AuthStorage.fetchUsageReports()` — proxies
83
- * to the broker's `/v1/usage` endpoint. The broker's egress IP isn't
84
- * rate-limited by Anthropic's per-IP `/usage` cap the way a heavy
85
- * residential laptop is, so all credentials surface every cycle.
86
- */
87
- fetchUsageReports(signal?: AbortSignal): Promise<UsageReport[] | null>;
88
- /**
89
- * Per-credential usage hook consumed by `AuthStorage.#getUsageReport`. Pulls
90
- * the aggregate broker `/v1/usage` once and serves all callers from the
91
- * same response (coalesced + cached), then matches the credential to a
92
- * report by provider + identity (accountId / email / projectId).
93
- *
94
- * The broker already aggregates with its own 30s TTL on the server side; our
95
- * 15s client TTL is below that so we usually re-use the broker's cache too.
96
- */
97
- getUsageReport(provider: Provider, credential: OAuthCredential, signal?: AbortSignal): Promise<UsageReport | null>;
98
- close(): void;
99
- }
@@ -1,32 +0,0 @@
1
- import type { AuthStorage } from "../auth-storage";
2
- export interface AuthBrokerServerOptions {
3
- /** Underlying credential storage (wraps the local SQLite store on the broker). */
4
- storage: AuthStorage;
5
- /** Listen address; accepts `host:port` or just `port`. */
6
- bind?: string;
7
- /** Accept any of these bearer tokens. Empty disables auth (loopback only). */
8
- bearerTokens: string[];
9
- /** Broker version string surfaced on `/v1/healthz`. */
10
- version?: string;
11
- /** Refresh credentials expiring within this window. Default 5 min. */
12
- refreshSkewMs?: number;
13
- /** Background refresh cadence. Default 60s. */
14
- refreshIntervalMs?: number;
15
- /** Disable the background refresher (e.g. for tests). */
16
- disableRefresher?: boolean;
17
- /**
18
- * Override SSE keepalive cadence in milliseconds for `/v1/snapshot/stream`.
19
- * Internal-only — tests use a short interval so they can assert heartbeats
20
- * without long sleeps. Default {@link DEFAULT_STREAM_KEEPALIVE_MS}.
21
- */
22
- streamKeepaliveMs?: number;
23
- }
24
- export interface AuthBrokerServerHandle {
25
- /** Bound URL (`http://host:port`). */
26
- url: string;
27
- port: number;
28
- hostname: string;
29
- close(): Promise<void>;
30
- }
31
- /** Boot the broker. Caller owns lifecycle; `handle.close()` to stop. */
32
- export declare function startAuthBroker(opts: AuthBrokerServerOptions): AuthBrokerServerHandle;
@@ -1,110 +0,0 @@
1
- /**
2
- * Wire types shared between the auth-broker server and clients.
3
- *
4
- * The broker holds OAuth refresh tokens and exposes a redacted snapshot;
5
- * clients use `access` tokens directly and call back to the broker when a
6
- * credential expires or a 401 surfaces on a supposedly-fresh credential.
7
- */
8
- import type { AuthCredential, AuthCredentialIfAbsentReason, AuthCredentialSnapshot, AuthCredentialSnapshotEntry } from "../auth-storage";
9
- import type { UsageReport } from "../usage";
10
- /** GET /v1/healthz response body. */
11
- export interface HealthzResponse {
12
- ok: boolean;
13
- version?: string;
14
- }
15
- export interface RefresherSchedule {
16
- enabled: boolean;
17
- intervalMs: number;
18
- skewMs: number;
19
- nextSweepInMs: number;
20
- }
21
- export type SnapshotEntry = AuthCredentialSnapshotEntry & {
22
- rotatesInMs: number | null;
23
- };
24
- /** GET /v1/snapshot response body. */
25
- export interface SnapshotResponse extends Omit<AuthCredentialSnapshot, "credentials"> {
26
- serverNowMs: number;
27
- refresher: RefresherSchedule;
28
- credentials: SnapshotEntry[];
29
- }
30
- /** GET /v1/usage response body — matches the local `AuthStorage.fetchUsageReports` shape. */
31
- export interface UsageResponse {
32
- generatedAt: number;
33
- reports: UsageReport[];
34
- }
35
- /** POST /v1/credential/:id/refresh response body. */
36
- export interface CredentialRefreshResponse {
37
- entry: AuthCredentialSnapshotEntry;
38
- }
39
- /** POST /v1/credential/:id/disable request body. */
40
- export interface CredentialDisableRequest {
41
- cause: string;
42
- }
43
- /** POST /v1/credential/:id/disable response body. */
44
- export interface CredentialDisableResponse {
45
- ok: boolean;
46
- }
47
- /**
48
- * POST /v1/credential request body. The OAuth `refresh` must be the *real*
49
- * refresh token (not the sentinel) — the broker is the canonical writer.
50
- */
51
- export interface CredentialUploadRequest {
52
- provider: string;
53
- credential: AuthCredential;
54
- }
55
- /** POST /v1/credential response body — redacted snapshot of the provider's rows after upsert. */
56
- export interface CredentialUploadResponse {
57
- entries: AuthCredentialSnapshotEntry[];
58
- }
59
- export interface CredentialIfAbsentUploadResponse {
60
- inserted: boolean;
61
- reason: AuthCredentialIfAbsentReason;
62
- entries: AuthCredentialSnapshotEntry[];
63
- }
64
- /**
65
- * SSE event kinds emitted on `GET /v1/snapshot/stream`. The same value is set
66
- * as the SSE `event:` name (load-bearing for clients) **and** embedded as a
67
- * `kind` field inside the JSON body so a Zod discriminated union can validate
68
- * the payload without consulting the line metadata.
69
- */
70
- export type SnapshotStreamEventKind = "snapshot" | "entry" | "removed";
71
- /** Initial frame emitted on connect — the full {@link SnapshotResponse}. */
72
- export interface SnapshotStreamSnapshotEvent extends SnapshotResponse {
73
- kind: "snapshot";
74
- }
75
- /** Single credential added/changed (upsert or refresh). */
76
- export interface SnapshotStreamEntryEvent {
77
- kind: "entry";
78
- generation: number;
79
- serverNowMs: number;
80
- refresher: RefresherSchedule;
81
- entry: SnapshotEntry;
82
- }
83
- /** Single credential disabled/deleted. */
84
- export interface SnapshotStreamRemovedEvent {
85
- kind: "removed";
86
- generation: number;
87
- serverNowMs: number;
88
- refresher: RefresherSchedule;
89
- id: number;
90
- }
91
- /** Discriminated union of every event the snapshot stream emits. */
92
- export type SnapshotStreamEvent = SnapshotStreamSnapshotEvent | SnapshotStreamEntryEvent | SnapshotStreamRemovedEvent;
93
- /**
94
- * Default bearer-protected route prefix. The broker exposes `/v1/healthz`
95
- * unauthenticated for liveness probes; everything else requires a bearer.
96
- */
97
- export declare const AUTH_BROKER_API_PREFIX = "/v1";
98
- /** Default port when none is configured. Loopback-only, no external exposure. */
99
- export declare const DEFAULT_AUTH_BROKER_BIND = "127.0.0.1:8765";
100
- /** Default broker→provider refresh skew. Refresh credentials this close to expiry. */
101
- export declare const DEFAULT_REFRESH_SKEW_MS: number;
102
- /** Default broker refresh-loop cadence. */
103
- export declare const DEFAULT_REFRESH_INTERVAL_MS = 60000;
104
- /** Keepalive cadence for `GET /v1/snapshot/stream` SSE comments. */
105
- export declare const DEFAULT_STREAM_KEEPALIVE_MS = 20000;
106
- /**
107
- * Bun.serve `idleTimeout` (seconds) used by the broker. Default Bun idle
108
- * timeout (10s) would close long-lived SSE connections between keepalives.
109
- */
110
- export declare const DEFAULT_SERVER_IDLE_TIMEOUT_S = 255;