@oyasmi/pipiclaw 0.5.9 → 0.6.1

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 (51) hide show
  1. package/dist/agent/channel-runner.d.ts +8 -0
  2. package/dist/agent/channel-runner.js +132 -24
  3. package/dist/agent/context-budget.d.ts +9 -0
  4. package/dist/agent/context-budget.js +31 -0
  5. package/dist/agent/session-events.js +11 -4
  6. package/dist/agent/type-guards.js +4 -2
  7. package/dist/agent/types.d.ts +10 -3
  8. package/dist/agent/types.js +1 -0
  9. package/dist/index.d.ts +3 -3
  10. package/dist/index.js +2 -2
  11. package/dist/memory/candidates.d.ts +8 -5
  12. package/dist/memory/candidates.js +92 -42
  13. package/dist/memory/consolidation.js +13 -4
  14. package/dist/memory/recall.d.ts +2 -2
  15. package/dist/memory/recall.js +2 -3
  16. package/dist/memory/session.js +2 -2
  17. package/dist/memory/sidecar-worker.d.ts +1 -0
  18. package/dist/memory/sidecar-worker.js +56 -1
  19. package/dist/paths.d.ts +1 -0
  20. package/dist/paths.js +1 -0
  21. package/dist/runtime/bootstrap.d.ts +1 -0
  22. package/dist/runtime/bootstrap.js +52 -13
  23. package/dist/runtime/delivery.js +101 -12
  24. package/dist/runtime/dingtalk.d.ts +11 -1
  25. package/dist/runtime/dingtalk.js +69 -24
  26. package/dist/runtime/events.d.ts +17 -2
  27. package/dist/runtime/events.js +107 -19
  28. package/dist/security/command-guard.js +4 -0
  29. package/dist/security/config.d.ts +6 -0
  30. package/dist/security/config.js +38 -6
  31. package/dist/security/path-guard.js +4 -0
  32. package/dist/security/platform.d.ts +1 -0
  33. package/dist/security/platform.js +3 -0
  34. package/dist/settings.d.ts +4 -1
  35. package/dist/settings.js +31 -6
  36. package/dist/shared/config-diagnostics.d.ts +7 -0
  37. package/dist/shared/config-diagnostics.js +3 -0
  38. package/dist/subagents/tool.d.ts +2 -0
  39. package/dist/subagents/tool.js +2 -3
  40. package/dist/tools/config.d.ts +7 -0
  41. package/dist/tools/config.js +63 -7
  42. package/dist/tools/index.d.ts +5 -0
  43. package/dist/tools/index.js +3 -2
  44. package/dist/web/client.d.ts +1 -0
  45. package/dist/web/client.js +30 -18
  46. package/dist/web/config.d.ts +1 -0
  47. package/dist/web/config.js +1 -0
  48. package/dist/web/fetch.d.ts +1 -0
  49. package/dist/web/fetch.js +7 -5
  50. package/dist/web/search-providers.js +6 -3
  51. package/package.json +1 -1
package/dist/settings.js CHANGED
@@ -8,6 +8,7 @@
8
8
  */
9
9
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
10
10
  import { dirname, join } from "path";
11
+ import * as log from "./log.js";
11
12
  const DEFAULT_COMPACTION = {
12
13
  enabled: true,
13
14
  reserveTokens: 16384,
@@ -40,18 +41,32 @@ const DEFAULT_SESSION_MEMORY = {
40
41
  */
41
42
  export class PipiclawSettingsManager {
42
43
  constructor(baseDir) {
44
+ this.loadErrors = [];
43
45
  this.settingsPath = join(baseDir, "settings.json");
44
46
  this.settings = this.load();
45
47
  }
46
48
  load() {
49
+ this.loadErrors = [];
47
50
  if (!existsSync(this.settingsPath)) {
48
51
  return {};
49
52
  }
50
53
  try {
51
54
  const content = readFileSync(this.settingsPath, "utf-8");
52
- return JSON.parse(content);
55
+ const parsed = JSON.parse(content);
56
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
57
+ this.loadErrors.push({
58
+ scope: "global",
59
+ error: new Error(`Expected a JSON object in ${this.settingsPath}`),
60
+ });
61
+ return {};
62
+ }
63
+ return parsed;
53
64
  }
54
- catch {
65
+ catch (error) {
66
+ this.loadErrors.push({
67
+ scope: "global",
68
+ error: error instanceof Error ? error : new Error(String(error)),
69
+ });
55
70
  return {};
56
71
  }
57
72
  }
@@ -64,12 +79,25 @@ export class PipiclawSettingsManager {
64
79
  writeFileSync(this.settingsPath, JSON.stringify(this.settings, null, 2), "utf-8");
65
80
  }
66
81
  catch (error) {
67
- console.error(`Warning: Could not save settings file: ${error}`);
82
+ log.logWarning(`Could not save settings file`, `${this.settingsPath}\n${String(error)}`);
68
83
  }
69
84
  }
70
85
  reload() {
71
86
  this.settings = this.load();
72
87
  }
88
+ drainErrors() {
89
+ const errors = this.loadErrors;
90
+ this.loadErrors = [];
91
+ return errors;
92
+ }
93
+ getDiagnostics() {
94
+ return this.loadErrors.map(({ error }) => ({
95
+ source: "settings",
96
+ path: this.settingsPath,
97
+ severity: "error",
98
+ message: error.message,
99
+ }));
100
+ }
73
101
  getCompactionSettings() {
74
102
  return {
75
103
  ...DEFAULT_COMPACTION,
@@ -377,7 +405,4 @@ export class PipiclawSettingsManager {
377
405
  flush() {
378
406
  return Promise.resolve();
379
407
  }
380
- drainErrors() {
381
- return [];
382
- }
383
408
  }
@@ -0,0 +1,7 @@
1
+ export interface ConfigDiagnostic {
2
+ source: "settings" | "tools" | "security";
3
+ path: string;
4
+ severity: "warning" | "error";
5
+ message: string;
6
+ }
7
+ export declare function formatConfigDiagnostic(diagnostic: ConfigDiagnostic): string;
@@ -0,0 +1,3 @@
1
+ export function formatConfigDiagnostic(diagnostic) {
2
+ return `${diagnostic.source}.json: ${diagnostic.message}`;
3
+ }
@@ -1,5 +1,6 @@
1
1
  import { type AgentEvent, type AgentMessage, type AgentTool } from "@mariozechner/pi-agent-core";
2
2
  import type { Api, Model } from "@mariozechner/pi-ai";
3
+ import type { MemoryCandidateStore } from "../memory/candidates.js";
3
4
  import type { Executor } from "../sandbox.js";
4
5
  import type { SecurityConfig } from "../security/types.js";
5
6
  import type { PipiclawMemoryRecallSettings } from "../settings.js";
@@ -44,6 +45,7 @@ export interface SubAgentToolOptions {
44
45
  channelDir: string;
45
46
  getSubAgentDiscovery?: () => SubAgentDiscoveryResult;
46
47
  getMemoryRecallSettings?: () => PipiclawMemoryRecallSettings;
48
+ memoryCandidateStore?: MemoryCandidateStore;
47
49
  securityConfig?: SecurityConfig;
48
50
  webConfig?: PipiclawWebToolsConfig;
49
51
  runtimeContext: {
@@ -1,7 +1,6 @@
1
1
  import { Agent } from "@mariozechner/pi-agent-core";
2
2
  import { convertToLlm } from "@mariozechner/pi-coding-agent";
3
3
  import { Type } from "@sinclair/typebox";
4
- import { createMemoryCandidateCache } from "../memory/candidates.js";
5
4
  import { readChannelSession } from "../memory/files.js";
6
5
  import { recallRelevantMemory } from "../memory/recall.js";
7
6
  import { formatModelReference } from "../models/utils.js";
@@ -187,7 +186,7 @@ function stripRuntimeContextWrapper(renderedText) {
187
186
  .replace(/\s*<\/runtime_context>$/i, "")
188
187
  .trim();
189
188
  }
190
- async function buildContextualBlocks(task, config, options, currentModel, candidateCache = createMemoryCandidateCache()) {
189
+ async function buildContextualBlocks(task, config, options, currentModel) {
191
190
  if (config.contextMode !== "contextual") {
192
191
  return [];
193
192
  }
@@ -226,7 +225,7 @@ async function buildContextualBlocks(task, config, options, currentModel, candid
226
225
  model: currentModel,
227
226
  resolveApiKey: options.resolveApiKey,
228
227
  allowedSources: ["workspace-memory", "channel-memory", "channel-history"],
229
- candidateCache,
228
+ candidateStore: options.memoryCandidateStore,
230
229
  });
231
230
  const recalledText = stripRuntimeContextWrapper(recalled.renderedText);
232
231
  if (recalledText) {
@@ -1,3 +1,4 @@
1
+ import type { ConfigDiagnostic } from "../shared/config-diagnostics.js";
1
2
  export type WebSearchProvider = "brave" | "tavily" | "jina" | "searxng" | "duckduckgo";
2
3
  export interface PipiclawWebSearchConfig {
3
4
  provider: WebSearchProvider;
@@ -10,6 +11,7 @@ export interface PipiclawWebFetchConfig {
10
11
  maxChars: number;
11
12
  timeoutMs: number;
12
13
  maxImageBytes: number;
14
+ maxResponseBytes: number;
13
15
  preferJina: boolean;
14
16
  enableJinaFallback: boolean;
15
17
  defaultExtractMode: "markdown" | "text";
@@ -25,6 +27,11 @@ export interface PipiclawToolsConfig {
25
27
  web: PipiclawWebToolsConfig;
26
28
  };
27
29
  }
30
+ export interface LoadedToolsConfig {
31
+ config: PipiclawToolsConfig;
32
+ diagnostics: ConfigDiagnostic[];
33
+ }
28
34
  export declare const DEFAULT_TOOLS_CONFIG: PipiclawToolsConfig;
29
35
  export declare function getToolsConfigPath(appHomeDir?: string): string;
36
+ export declare function loadToolsConfigWithDiagnostics(appHomeDir?: string): LoadedToolsConfig;
30
37
  export declare function loadToolsConfig(appHomeDir?: string): PipiclawToolsConfig;
@@ -19,6 +19,7 @@ export const DEFAULT_TOOLS_CONFIG = {
19
19
  maxChars: 50_000,
20
20
  timeoutMs: 30_000,
21
21
  maxImageBytes: 10 * 1024 * 1024,
22
+ maxResponseBytes: 5 * 1024 * 1024,
22
23
  preferJina: false,
23
24
  enableJinaFallback: false,
24
25
  defaultExtractMode: "markdown",
@@ -52,8 +53,17 @@ function asOptionalProxy(value) {
52
53
  const trimmed = value.trim();
53
54
  return trimmed.length > 0 ? trimmed : null;
54
55
  }
55
- function mergeToolsConfig(source) {
56
+ function pushInvalidValueDiagnostic(diagnostics, configPath, field, message) {
57
+ diagnostics.push({
58
+ source: "tools",
59
+ path: configPath,
60
+ severity: "warning",
61
+ message: `${field}: ${message}`,
62
+ });
63
+ }
64
+ function mergeToolsConfig(source, configPath, diagnostics) {
56
65
  if (!isRecord(source)) {
66
+ pushInvalidValueDiagnostic(diagnostics, configPath, "root", "expected a JSON object; using defaults");
57
67
  return DEFAULT_TOOLS_CONFIG;
58
68
  }
59
69
  const tools = isRecord(source.tools) ? source.tools : {};
@@ -63,8 +73,37 @@ function mergeToolsConfig(source) {
63
73
  const providerValue = asTrimmedString(search.provider, DEFAULT_TOOLS_CONFIG.tools.web.search.provider).toLowerCase();
64
74
  const provider = WEB_SEARCH_PROVIDERS.includes(providerValue)
65
75
  ? providerValue
66
- : DEFAULT_TOOLS_CONFIG.tools.web.search.provider;
76
+ : (() => {
77
+ if (search.provider !== undefined) {
78
+ pushInvalidValueDiagnostic(diagnostics, configPath, "tools.web.search.provider", `unknown provider "${String(search.provider)}"; using ${DEFAULT_TOOLS_CONFIG.tools.web.search.provider}`);
79
+ }
80
+ return DEFAULT_TOOLS_CONFIG.tools.web.search.provider;
81
+ })();
67
82
  const defaultExtractMode = asTrimmedString(fetch.defaultExtractMode, DEFAULT_TOOLS_CONFIG.tools.web.fetch.defaultExtractMode);
83
+ if (web.proxy !== undefined && web.proxy !== null && typeof web.proxy !== "string") {
84
+ pushInvalidValueDiagnostic(diagnostics, configPath, "tools.web.proxy", "expected a string or null; using null");
85
+ }
86
+ if (search.maxResults !== undefined && clampInteger(search.maxResults, -1, 1, 10) === -1) {
87
+ pushInvalidValueDiagnostic(diagnostics, configPath, "tools.web.search.maxResults", "expected an integer between 1 and 10; using default");
88
+ }
89
+ if (search.timeoutMs !== undefined && clampInteger(search.timeoutMs, -1, 1) === -1) {
90
+ pushInvalidValueDiagnostic(diagnostics, configPath, "tools.web.search.timeoutMs", "expected a positive integer; using default");
91
+ }
92
+ if (fetch.maxChars !== undefined && clampInteger(fetch.maxChars, -1, 100) === -1) {
93
+ pushInvalidValueDiagnostic(diagnostics, configPath, "tools.web.fetch.maxChars", "expected an integer >= 100; using default");
94
+ }
95
+ if (fetch.timeoutMs !== undefined && clampInteger(fetch.timeoutMs, -1, 1) === -1) {
96
+ pushInvalidValueDiagnostic(diagnostics, configPath, "tools.web.fetch.timeoutMs", "expected a positive integer; using default");
97
+ }
98
+ if (fetch.maxImageBytes !== undefined && clampInteger(fetch.maxImageBytes, -1, 1) === -1) {
99
+ pushInvalidValueDiagnostic(diagnostics, configPath, "tools.web.fetch.maxImageBytes", "expected a positive integer; using default");
100
+ }
101
+ if (fetch.maxResponseBytes !== undefined && clampInteger(fetch.maxResponseBytes, -1, 1) === -1) {
102
+ pushInvalidValueDiagnostic(diagnostics, configPath, "tools.web.fetch.maxResponseBytes", "expected a positive integer; using default");
103
+ }
104
+ if (fetch.defaultExtractMode !== undefined && defaultExtractMode !== "text" && defaultExtractMode !== "markdown") {
105
+ pushInvalidValueDiagnostic(diagnostics, configPath, "tools.web.fetch.defaultExtractMode", `expected "markdown" or "text"; using ${DEFAULT_TOOLS_CONFIG.tools.web.fetch.defaultExtractMode}`);
106
+ }
68
107
  return {
69
108
  tools: {
70
109
  web: {
@@ -81,6 +120,7 @@ function mergeToolsConfig(source) {
81
120
  maxChars: clampInteger(fetch.maxChars, DEFAULT_TOOLS_CONFIG.tools.web.fetch.maxChars, 100),
82
121
  timeoutMs: clampInteger(fetch.timeoutMs, DEFAULT_TOOLS_CONFIG.tools.web.fetch.timeoutMs, 1),
83
122
  maxImageBytes: clampInteger(fetch.maxImageBytes, DEFAULT_TOOLS_CONFIG.tools.web.fetch.maxImageBytes, 1),
123
+ maxResponseBytes: clampInteger(fetch.maxResponseBytes, DEFAULT_TOOLS_CONFIG.tools.web.fetch.maxResponseBytes, 1),
84
124
  preferJina: typeof fetch.preferJina === "boolean"
85
125
  ? fetch.preferJina
86
126
  : DEFAULT_TOOLS_CONFIG.tools.web.fetch.preferJina,
@@ -98,17 +138,33 @@ function mergeToolsConfig(source) {
98
138
  export function getToolsConfigPath(appHomeDir = APP_HOME_DIR) {
99
139
  return appHomeDir === APP_HOME_DIR ? TOOLS_CONFIG_PATH : join(appHomeDir, "tools.json");
100
140
  }
101
- export function loadToolsConfig(appHomeDir = APP_HOME_DIR) {
141
+ export function loadToolsConfigWithDiagnostics(appHomeDir = APP_HOME_DIR) {
102
142
  const configPath = getToolsConfigPath(appHomeDir);
103
143
  if (!existsSync(configPath)) {
104
- return DEFAULT_TOOLS_CONFIG;
144
+ return { config: DEFAULT_TOOLS_CONFIG, diagnostics: [] };
105
145
  }
106
146
  try {
107
147
  const raw = JSON.parse(readFileSync(configPath, "utf-8"));
108
- return mergeToolsConfig(raw);
148
+ const diagnostics = [];
149
+ return {
150
+ config: mergeToolsConfig(raw, configPath, diagnostics),
151
+ diagnostics,
152
+ };
109
153
  }
110
154
  catch (error) {
111
- console.warn(`Failed to load tools config from ${configPath}: ${error}`);
112
- return DEFAULT_TOOLS_CONFIG;
155
+ return {
156
+ config: DEFAULT_TOOLS_CONFIG,
157
+ diagnostics: [
158
+ {
159
+ source: "tools",
160
+ path: configPath,
161
+ severity: "error",
162
+ message: error instanceof Error ? error.message : String(error),
163
+ },
164
+ ],
165
+ };
113
166
  }
114
167
  }
168
+ export function loadToolsConfig(appHomeDir = APP_HOME_DIR) {
169
+ return loadToolsConfigWithDiagnostics(appHomeDir).config;
170
+ }
@@ -1,9 +1,11 @@
1
1
  import type { AgentTool } from "@mariozechner/pi-agent-core";
2
2
  import type { Api, Model } from "@mariozechner/pi-ai";
3
+ import type { MemoryCandidateStore } from "../memory/candidates.js";
3
4
  import type { Executor, SandboxConfig } from "../sandbox.js";
4
5
  import type { SecurityConfig, SecurityRuntimeContext } from "../security/types.js";
5
6
  import type { PipiclawMemoryRecallSettings } from "../settings.js";
6
7
  import type { SubAgentDiscoveryResult } from "../subagents/discovery.js";
8
+ import type { PipiclawToolsConfig } from "./config.js";
7
9
  export interface CreatePipiclawToolsOptions {
8
10
  executor: Executor;
9
11
  getCurrentModel: () => Model<Api>;
@@ -16,6 +18,9 @@ export interface CreatePipiclawToolsOptions {
16
18
  sandboxConfig: SandboxConfig;
17
19
  getSubAgentDiscovery: () => SubAgentDiscoveryResult;
18
20
  getMemoryRecallSettings: () => PipiclawMemoryRecallSettings;
21
+ memoryCandidateStore: MemoryCandidateStore;
22
+ securityConfig?: SecurityConfig;
23
+ toolsConfig?: PipiclawToolsConfig;
19
24
  }
20
25
  export interface CreatePipiclawBaseToolsOptions {
21
26
  securityConfig?: SecurityConfig;
@@ -25,8 +25,8 @@ export function createPipiclawBaseTools(executor, options = {}) {
25
25
  ];
26
26
  }
27
27
  export function createPipiclawTools(options) {
28
- const securityConfig = loadSecurityConfig(APP_HOME_DIR);
29
- const toolsConfig = loadToolsConfig(APP_HOME_DIR);
28
+ const securityConfig = options.securityConfig ?? loadSecurityConfig(APP_HOME_DIR);
29
+ const toolsConfig = options.toolsConfig ?? loadToolsConfig(APP_HOME_DIR);
30
30
  const securityContext = {
31
31
  workspaceDir: options.workspaceDir,
32
32
  workspacePath: options.workspacePath,
@@ -65,6 +65,7 @@ export function createPipiclawTools(options) {
65
65
  channelDir: options.channelDir,
66
66
  getSubAgentDiscovery: options.getSubAgentDiscovery,
67
67
  getMemoryRecallSettings: options.getMemoryRecallSettings,
68
+ memoryCandidateStore: options.memoryCandidateStore,
68
69
  securityConfig,
69
70
  webConfig: toolsConfig.tools.web,
70
71
  runtimeContext: {
@@ -23,6 +23,7 @@ export interface WebHttpRequestOptions {
23
23
  timeoutMs: number;
24
24
  signal?: AbortSignal;
25
25
  maxRedirects?: number;
26
+ maxResponseBytes?: number;
26
27
  }
27
28
  export declare class WebHttpClient {
28
29
  private readonly context;
@@ -108,24 +108,36 @@ export class WebHttpClient {
108
108
  throw error;
109
109
  }
110
110
  const agent = getProxyAgent(currentUrl, this.context.webConfig.proxy);
111
- const response = await axios.request({
112
- method,
113
- url: currentUrl,
114
- data,
115
- headers: {
116
- "User-Agent": WEB_USER_AGENT,
117
- Accept: "*/*",
118
- ...options.headers,
119
- },
120
- responseType: "arraybuffer",
121
- validateStatus: () => true,
122
- timeout: options.timeoutMs,
123
- signal: options.signal,
124
- maxRedirects: 0,
125
- proxy: false,
126
- httpAgent: agent,
127
- httpsAgent: agent,
128
- });
111
+ let response;
112
+ try {
113
+ response = await axios.request({
114
+ method,
115
+ url: currentUrl,
116
+ data,
117
+ headers: {
118
+ "User-Agent": WEB_USER_AGENT,
119
+ Accept: "*/*",
120
+ ...options.headers,
121
+ },
122
+ responseType: "arraybuffer",
123
+ validateStatus: () => true,
124
+ timeout: options.timeoutMs,
125
+ signal: options.signal,
126
+ maxRedirects: 0,
127
+ maxContentLength: options.maxResponseBytes ?? Number.POSITIVE_INFINITY,
128
+ proxy: false,
129
+ httpAgent: agent,
130
+ httpsAgent: agent,
131
+ });
132
+ }
133
+ catch (error) {
134
+ if (options.maxResponseBytes &&
135
+ typeof error?.message === "string" &&
136
+ error.message.includes("maxContentLength")) {
137
+ throw new Error(`Response exceeds maxResponseBytes (${options.maxResponseBytes} bytes)`);
138
+ }
139
+ throw error;
140
+ }
129
141
  const headers = normalizeHeaders(response.headers);
130
142
  const body = Buffer.isBuffer(response.data) ? response.data : Buffer.from(response.data);
131
143
  if (isRedirectStatus(response.status) && headers.location) {
@@ -10,6 +10,7 @@ export interface ResolvedWebFetchRequest {
10
10
  maxChars: number;
11
11
  timeoutMs: number;
12
12
  maxImageBytes: number;
13
+ maxResponseBytes: number;
13
14
  preferJina: boolean;
14
15
  enableJinaFallback: boolean;
15
16
  }
@@ -12,6 +12,7 @@ export function resolveWebFetchRequest(config, url, extractMode, maxChars) {
12
12
  maxChars: clamp(maxChars, config.maxChars, 100),
13
13
  timeoutMs: config.timeoutMs,
14
14
  maxImageBytes: config.maxImageBytes,
15
+ maxResponseBytes: config.maxResponseBytes,
15
16
  preferJina: config.preferJina,
16
17
  enableJinaFallback: config.enableJinaFallback,
17
18
  };
@@ -17,6 +17,7 @@ export declare function runWebFetch(context: WebFetchExecutionContext, request:
17
17
  extractMode: "markdown" | "text";
18
18
  maxChars: number;
19
19
  maxImageBytes: number;
20
+ maxResponseBytes: number;
20
21
  preferJina: boolean;
21
22
  enableJinaFallback: boolean;
22
23
  }, signal?: AbortSignal): Promise<WebFetchOutput>;
package/dist/web/fetch.js CHANGED
@@ -22,7 +22,7 @@ function isHtmlContent(contentType, body) {
22
22
  .toLowerCase();
23
23
  return head.startsWith("<!doctype") || head.startsWith("<html");
24
24
  }
25
- async function tryFetchViaJina(context, url, maxChars, signal) {
25
+ async function tryFetchViaJina(context, url, maxChars, maxResponseBytes, signal) {
26
26
  const client = createWebHttpClient({
27
27
  webConfig: context.webConfig,
28
28
  securityConfig: context.securityConfig,
@@ -37,6 +37,7 @@ async function tryFetchViaJina(context, url, maxChars, signal) {
37
37
  url: `https://r.jina.ai/${url}`,
38
38
  headers,
39
39
  timeoutMs: context.webConfig.fetch.timeoutMs,
40
+ maxResponseBytes,
40
41
  signal,
41
42
  });
42
43
  if (response.status < 200 || response.status >= 300 || !data.data?.content) {
@@ -59,7 +60,7 @@ async function tryFetchViaJina(context, url, maxChars, signal) {
59
60
  },
60
61
  };
61
62
  }
62
- async function fetchDirect(context, url, extractMode, maxChars, maxImageBytes, signal) {
63
+ async function fetchDirect(context, url, extractMode, maxChars, maxImageBytes, maxResponseBytes, signal) {
63
64
  const client = createWebHttpClient({
64
65
  webConfig: context.webConfig,
65
66
  securityConfig: context.securityConfig,
@@ -69,6 +70,7 @@ async function fetchDirect(context, url, extractMode, maxChars, maxImageBytes, s
69
70
  const response = await client.request({
70
71
  url,
71
72
  timeoutMs: context.webConfig.fetch.timeoutMs,
73
+ maxResponseBytes,
72
74
  signal,
73
75
  });
74
76
  if (response.status < 200 || response.status >= 300) {
@@ -127,19 +129,19 @@ async function fetchDirect(context, url, extractMode, maxChars, maxImageBytes, s
127
129
  }
128
130
  export async function runWebFetch(context, request, signal) {
129
131
  if (request.preferJina) {
130
- const jinaResult = await tryFetchViaJina(context, request.url, request.maxChars, signal);
132
+ const jinaResult = await tryFetchViaJina(context, request.url, request.maxChars, request.maxResponseBytes, signal);
131
133
  if (jinaResult) {
132
134
  return jinaResult;
133
135
  }
134
136
  }
135
137
  try {
136
- return await fetchDirect(context, request.url, request.extractMode, request.maxChars, request.maxImageBytes, signal);
138
+ return await fetchDirect(context, request.url, request.extractMode, request.maxChars, request.maxImageBytes, request.maxResponseBytes, signal);
137
139
  }
138
140
  catch (error) {
139
141
  if (!request.enableJinaFallback) {
140
142
  throw error;
141
143
  }
142
- const jinaResult = await tryFetchViaJina(context, request.url, request.maxChars, signal);
144
+ const jinaResult = await tryFetchViaJina(context, request.url, request.maxChars, request.maxResponseBytes, signal);
143
145
  if (jinaResult) {
144
146
  return jinaResult;
145
147
  }
@@ -19,6 +19,9 @@ function normalizeResult(item) {
19
19
  snippet,
20
20
  };
21
21
  }
22
+ function isProviderConfigStatus(status) {
23
+ return status === 400 || status === 401 || status === 403 || status === 422 || status === 429;
24
+ }
22
25
  class BraveSearchProvider {
23
26
  constructor(context) {
24
27
  this.context = context;
@@ -38,7 +41,7 @@ class BraveSearchProvider {
38
41
  signal,
39
42
  });
40
43
  if (response.status < 200 || response.status >= 300) {
41
- throw new WebSearchProviderError("provider", `Brave search failed with HTTP ${response.status}`);
44
+ throw new WebSearchProviderError(isProviderConfigStatus(response.status) ? "config" : "provider", `Brave search failed with HTTP ${response.status}`);
42
45
  }
43
46
  return (data.web?.results ?? [])
44
47
  .map((item) => normalizeResult({
@@ -71,7 +74,7 @@ class TavilySearchProvider {
71
74
  signal,
72
75
  });
73
76
  if (response.status < 200 || response.status >= 300) {
74
- throw new WebSearchProviderError("provider", `Tavily search failed with HTTP ${response.status}`);
77
+ throw new WebSearchProviderError(isProviderConfigStatus(response.status) ? "config" : "provider", `Tavily search failed with HTTP ${response.status}`);
75
78
  }
76
79
  return (data.results ?? [])
77
80
  .map((item) => normalizeResult({
@@ -101,7 +104,7 @@ class JinaSearchProvider {
101
104
  signal,
102
105
  });
103
106
  if (response.status < 200 || response.status >= 300) {
104
- throw new WebSearchProviderError("provider", `Jina search failed with HTTP ${response.status}`);
107
+ throw new WebSearchProviderError(isProviderConfigStatus(response.status) ? "config" : "provider", `Jina search failed with HTTP ${response.status}`);
105
108
  }
106
109
  return (data.data ?? [])
107
110
  .map((item) => normalizeResult({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oyasmi/pipiclaw",
3
- "version": "0.5.9",
3
+ "version": "0.6.1",
4
4
  "description": "An AI assistant runtime for coding and team workflows, with DingTalk AI Cards, sub-agents, memory, and scheduled events.",
5
5
  "type": "module",
6
6
  "bin": {