@ganju/utils 0.0.13 → 0.0.15

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 (70) hide show
  1. package/dist/channelCustomer.d.ts +99 -0
  2. package/dist/channelCustomer.d.ts.map +1 -0
  3. package/dist/channelCustomer.js +533 -0
  4. package/dist/channelDebounce.d.ts +7 -0
  5. package/dist/channelDebounce.d.ts.map +1 -1
  6. package/dist/chunkOverlap.d.ts +2 -0
  7. package/dist/chunkOverlap.d.ts.map +1 -0
  8. package/dist/chunkOverlap.js +9 -0
  9. package/dist/chunking.d.ts.map +1 -1
  10. package/dist/chunking.js +24 -4
  11. package/dist/citations.d.ts +28 -0
  12. package/dist/citations.d.ts.map +1 -0
  13. package/dist/citations.js +111 -0
  14. package/dist/constants.d.ts +26 -0
  15. package/dist/constants.d.ts.map +1 -1
  16. package/dist/constants.js +108 -10
  17. package/dist/index.d.ts +263 -31
  18. package/dist/index.d.ts.map +1 -1
  19. package/dist/index.js +70 -0
  20. package/dist/jsonSchemaToZodShape.d.ts +5 -1
  21. package/dist/jsonSchemaToZodShape.d.ts.map +1 -1
  22. package/dist/jsonSchemaToZodShape.js +21 -9
  23. package/dist/pagination.d.ts +47 -0
  24. package/dist/pagination.d.ts.map +1 -0
  25. package/dist/pagination.js +160 -0
  26. package/dist/processQueueBatch.d.ts +6 -1
  27. package/dist/processQueueBatch.d.ts.map +1 -1
  28. package/dist/processQueueBatch.js +23 -10
  29. package/dist/queueFailure.d.ts +14 -0
  30. package/dist/queueFailure.d.ts.map +1 -0
  31. package/dist/queueFailure.js +43 -0
  32. package/dist/resourceRerank.d.ts.map +1 -1
  33. package/dist/resourceRerank.js +3 -1
  34. package/dist/schema.d.ts +90 -26
  35. package/dist/schema.d.ts.map +1 -1
  36. package/dist/schema.js +87 -6
  37. package/dist/ssrf.d.ts +1 -0
  38. package/dist/ssrf.d.ts.map +1 -1
  39. package/dist/ssrf.js +48 -12
  40. package/dist/toolConfirmation.d.ts +48 -0
  41. package/dist/toolConfirmation.d.ts.map +1 -1
  42. package/dist/toolConfirmation.js +75 -1
  43. package/dist/toolCount.d.ts +13 -0
  44. package/dist/toolCount.d.ts.map +1 -0
  45. package/dist/toolCount.js +45 -0
  46. package/dist/toolLint.d.ts +1 -1
  47. package/dist/toolLint.d.ts.map +1 -1
  48. package/dist/toolLint.js +37 -3
  49. package/dist/untrustedContent.d.ts +135 -0
  50. package/dist/untrustedContent.d.ts.map +1 -0
  51. package/dist/untrustedContent.js +402 -0
  52. package/package.json +2 -1
  53. package/src/channelCustomer.ts +604 -0
  54. package/src/channelDebounce.ts +17 -0
  55. package/src/chunkOverlap.ts +6 -0
  56. package/src/chunking.ts +25 -4
  57. package/src/citations.ts +127 -0
  58. package/src/constants.ts +112 -10
  59. package/src/index.ts +152 -2
  60. package/src/jsonSchemaToZodShape.ts +31 -13
  61. package/src/pagination.ts +193 -0
  62. package/src/processQueueBatch.ts +42 -12
  63. package/src/queueFailure.ts +63 -0
  64. package/src/resourceRerank.ts +5 -1
  65. package/src/schema.ts +100 -4
  66. package/src/ssrf.ts +40 -11
  67. package/src/toolConfirmation.ts +123 -0
  68. package/src/toolCount.ts +53 -0
  69. package/src/toolLint.ts +46 -3
  70. package/src/untrustedContent.ts +476 -0
package/dist/index.js CHANGED
@@ -17,6 +17,8 @@ const coerce_1 = require("./coerce");
17
17
  const validateMessageVariables_1 = require("./validateMessageVariables");
18
18
  const jsonSchemaToZodShape_1 = require("./jsonSchemaToZodShape");
19
19
  const mcpProxy_1 = require("./mcpProxy");
20
+ const toolCount_1 = require("./toolCount");
21
+ const pagination_1 = require("./pagination");
20
22
  const customCodeToken_1 = require("./customCodeToken");
21
23
  const containmentToken_1 = require("./containmentToken");
22
24
  const oauthProviders_1 = require("./oauthProviders");
@@ -45,11 +47,15 @@ const l2Normalize_1 = require("./l2Normalize");
45
47
  const resourceRerank_1 = require("./resourceRerank");
46
48
  const retry_1 = require("./retry");
47
49
  const processQueueBatch_1 = require("./processQueueBatch");
50
+ const queueFailure_1 = require("./queueFailure");
48
51
  const channelNotifier_1 = require("./channelNotifier");
49
52
  const channelDebounce_1 = require("./channelDebounce");
50
53
  const toolConfirmation_1 = require("./toolConfirmation");
54
+ const untrustedContent_1 = require("./untrustedContent");
55
+ const channelCustomer_1 = require("./channelCustomer");
51
56
  const toolLint_1 = require("./toolLint");
52
57
  const sources_1 = require("./sources");
58
+ const citations_1 = require("./citations");
53
59
  const oauth_1 = require("./oauth");
54
60
  const vendorTimeZone_1 = require("./vendorTimeZone");
55
61
  const planLimitError_1 = require("./planLimitError");
@@ -93,6 +99,12 @@ exports.utils = {
93
99
  jsonSchemaToZodShape: jsonSchemaToZodShape_1.jsonSchemaToZodShape,
94
100
  validateAgainstJsonSchema: jsonSchemaToZodShape_1.validateAgainstJsonSchema,
95
101
  buildProxyToolName: mcpProxy_1.buildProxyToolName,
102
+ countInstallTools: toolCount_1.countInstallTools,
103
+ capText: pagination_1.capText,
104
+ findNextCursor: pagination_1.findNextCursor,
105
+ paginate: pagination_1.paginate,
106
+ InvalidCursorError: pagination_1.InvalidCursorError,
107
+ listResourcesPage: pagination_1.listResourcesPage,
96
108
  mintCustomCodeToken: customCodeToken_1.mintCustomCodeToken,
97
109
  verifyCustomCodeToken: customCodeToken_1.verifyCustomCodeToken,
98
110
  customCodeScriptName: customCodeToken_1.customCodeScriptName,
@@ -117,6 +129,7 @@ exports.utils = {
117
129
  validateProjectPath: customCodeProject_1.validateProjectPath,
118
130
  projectPathIssue: customCodeProject_1.projectPathIssue,
119
131
  ipv4InPrivateRange: ssrf_1.ipv4InPrivateRange,
132
+ isBlockedUrl: ssrf_1.isBlockedUrl,
120
133
  isBlockedHost: ssrf_1.isBlockedHost,
121
134
  requiresLlmBaseUrl: llmBaseUrl_1.requiresLlmBaseUrl,
122
135
  isAllowedLlmBaseUrl: llmBaseUrl_1.isAllowedLlmBaseUrl,
@@ -158,6 +171,8 @@ exports.utils = {
158
171
  isRateLimitError: retry_1.isRateLimitError,
159
172
  withRateLimitRetry: retry_1.withRateLimitRetry,
160
173
  processQueueBatch: processQueueBatch_1.processQueueBatch,
174
+ PermanentJobError: queueFailure_1.PermanentJobError,
175
+ isPermanentJobError: queueFailure_1.isPermanentJobError,
161
176
  getToolStatusMessage: channelNotifier_1.getToolStatusMessage,
162
177
  resolveDebounceMs: channelDebounce_1.resolveDebounceMs,
163
178
  channelBufferKey: channelDebounce_1.channelBufferKey,
@@ -172,7 +187,59 @@ exports.utils = {
172
187
  annotationsForEffect: toolConfirmation_1.annotationsForEffect,
173
188
  TOOL_EFFECTS: toolConfirmation_1.TOOL_EFFECTS,
174
189
  isSensitiveTool: toolConfirmation_1.isSensitiveTool,
190
+ proxiedToolAnnotations: toolConfirmation_1.proxiedToolAnnotations,
191
+ proxiedToolTrust: toolConfirmation_1.proxiedToolTrust,
192
+ isSensitiveProxiedTool: toolConfirmation_1.isSensitiveProxiedTool,
193
+ keepProxiedToolMarks: toolConfirmation_1.keepProxiedToolMarks,
194
+ toolApprovalStatus: toolConfirmation_1.toolApprovalStatus,
195
+ toolApprovalUrl: toolConfirmation_1.toolApprovalUrl,
175
196
  stableJson: toolConfirmation_1.stableJson,
197
+ wrapUntrustedContent: untrustedContent_1.wrapUntrustedContent,
198
+ unwrapUntrustedContent: untrustedContent_1.unwrapUntrustedContent,
199
+ isUntrustedToolResult: untrustedContent_1.isUntrustedToolResult,
200
+ findInstructionLikeText: untrustedContent_1.findInstructionLikeText,
201
+ UNTRUSTED_CONTENT_TAG: untrustedContent_1.UNTRUSTED_CONTENT_TAG,
202
+ UNTRUSTED_CONTENT_META_KEY: untrustedContent_1.UNTRUSTED_CONTENT_META_KEY,
203
+ UNTRUSTED_CONTENT_NOTE: untrustedContent_1.UNTRUSTED_CONTENT_NOTE,
204
+ INSTRUCTION_WARNING_METADATA_KEY: untrustedContent_1.INSTRUCTION_WARNING_METADATA_KEY,
205
+ untrustedToolResult: untrustedContent_1.untrustedToolResult,
206
+ ownWritingToolResult: untrustedContent_1.ownWritingToolResult,
207
+ labelOwnToolResult: untrustedContent_1.labelOwnToolResult,
208
+ labelProxiedToolResult: untrustedContent_1.labelProxiedToolResult,
209
+ untrustedErrorResult: untrustedContent_1.untrustedErrorResult,
210
+ readsLikeInstructions: untrustedContent_1.readsLikeInstructions,
211
+ labelResourceContents: untrustedContent_1.labelResourceContents,
212
+ PARTICIPANT_DAILY_LIMIT_DEFAULT: channelCustomer_1.PARTICIPANT_DAILY_LIMIT_DEFAULT,
213
+ PARTICIPANT_DAILY_LIMIT_MAX: channelCustomer_1.PARTICIPANT_DAILY_LIMIT_MAX,
214
+ PARTICIPANT_DAILY_LIMIT_OFF: channelCustomer_1.PARTICIPANT_DAILY_LIMIT_OFF,
215
+ PARTICIPANT_DAILY_LIMIT_WINDOW_MS: channelCustomer_1.PARTICIPANT_DAILY_LIMIT_WINDOW_MS,
216
+ PARTICIPANT_LIMIT_METADATA_KEY: channelCustomer_1.PARTICIPANT_LIMIT_METADATA_KEY,
217
+ resolveParticipantDailyLimit: channelCustomer_1.resolveParticipantDailyLimit,
218
+ resolveParticipantMemoryEnabled: channelCustomer_1.resolveParticipantMemoryEnabled,
219
+ VOICE_NOTE_MAX_BYTES: channelCustomer_1.VOICE_NOTE_MAX_BYTES,
220
+ VOICE_NOTE_MAX_SECONDS: channelCustomer_1.VOICE_NOTE_MAX_SECONDS,
221
+ audioDurationSeconds: channelCustomer_1.audioDurationSeconds,
222
+ oggDurationSeconds: channelCustomer_1.oggDurationSeconds,
223
+ VOICE_NOTE_TRANSCRIPTION_PROMPT: channelCustomer_1.VOICE_NOTE_TRANSCRIPTION_PROMPT,
224
+ VOICE_NOTE_METADATA_KEY: channelCustomer_1.VOICE_NOTE_METADATA_KEY,
225
+ VOICE_NOTE_UNREADABLE_METADATA_KEY: channelCustomer_1.VOICE_NOTE_UNREADABLE_METADATA_KEY,
226
+ PARTICIPANT_MEMORY_TOOL_NAME: channelCustomer_1.PARTICIPANT_MEMORY_TOOL_NAME,
227
+ PARTICIPANT_MEMORY_MAX_FACTS: channelCustomer_1.PARTICIPANT_MEMORY_MAX_FACTS,
228
+ PARTICIPANT_MEMORY_TOOL_DESCRIPTION: channelCustomer_1.PARTICIPANT_MEMORY_TOOL_DESCRIPTION,
229
+ PARTICIPANT_MEMORY_TOOL_SCHEMA: channelCustomer_1.PARTICIPANT_MEMORY_TOOL_SCHEMA,
230
+ PARTICIPANT_MEMORY_GROUP_NOTE: channelCustomer_1.PARTICIPANT_MEMORY_GROUP_NOTE,
231
+ readParticipantMemory: channelCustomer_1.readParticipantMemory,
232
+ applyParticipantMemoryUpdate: channelCustomer_1.applyParticipantMemoryUpdate,
233
+ redactParticipantMemoryArgs: channelCustomer_1.redactParticipantMemoryArgs,
234
+ redactParticipantMemoryConfirmation: channelCustomer_1.redactParticipantMemoryConfirmation,
235
+ participantMemoryUpdateReadsLikeInstructions: channelCustomer_1.participantMemoryUpdateReadsLikeInstructions,
236
+ CONVERSATION_UNTRUSTED_WINDOW_MS: channelCustomer_1.CONVERSATION_UNTRUSTED_WINDOW_MS,
237
+ CONVERSATION_UNTRUSTED_READ_AT_KEY: channelCustomer_1.CONVERSATION_UNTRUSTED_READ_AT_KEY,
238
+ conversationReadUntrustedRecently: channelCustomer_1.conversationReadUntrustedRecently,
239
+ formatParticipantMemoryPrompt: channelCustomer_1.formatParticipantMemoryPrompt,
240
+ participantLimitNotice: channelCustomer_1.participantLimitNotice,
241
+ voiceNoteUnreadableNotice: channelCustomer_1.voiceNoteUnreadableNotice,
242
+ voiceNoteTooLongNotice: channelCustomer_1.voiceNoteTooLongNotice,
176
243
  lintTools: toolLint_1.lintTools,
177
244
  TOOL_LINT_RULES: toolLint_1.TOOL_LINT_RULES,
178
245
  TOOL_LINT_MAX_TOOLS: toolLint_1.TOOL_LINT_MAX_TOOLS,
@@ -183,6 +250,9 @@ exports.utils = {
183
250
  buildResourceDownloadUrl: sources_1.buildResourceDownloadUrl,
184
251
  formatSourcesAsMarkdown: sources_1.formatSourcesAsMarkdown,
185
252
  formatSourcesAsButtons: sources_1.formatSourcesAsButtons,
253
+ citesUrl: citations_1.citesUrl,
254
+ citedPages: citations_1.citedPages,
255
+ selectCitedSources: citations_1.selectCitedSources,
186
256
  refreshOAuthToken: oauth_1.refreshOAuthToken,
187
257
  OAuthReauthRequiredError: oauth_1.OAuthReauthRequiredError,
188
258
  buildReauthMetadata: oauth_1.buildReauthMetadata,
@@ -1,6 +1,6 @@
1
1
  import * as z from 'zod';
2
2
  export type JsonSchemaProperty = {
3
- type: 'string' | 'number' | 'boolean' | 'object' | 'array';
3
+ type: 'string' | 'number' | 'integer' | 'boolean' | 'object' | 'array';
4
4
  description?: string;
5
5
  minimum?: number;
6
6
  maximum?: number;
@@ -9,6 +9,10 @@ export type JsonSchemaProperty = {
9
9
  pattern?: string;
10
10
  enum?: string[];
11
11
  items?: JsonSchemaProperty;
12
+ minItems?: number;
13
+ maxItems?: number;
14
+ properties?: Record<string, JsonSchemaProperty>;
15
+ required?: string[];
12
16
  };
13
17
  export type JsonSchema = {
14
18
  type: 'object';
@@ -1 +1 @@
1
- {"version":3,"file":"jsonSchemaToZodShape.d.ts","sourceRoot":"","sources":["../src/jsonSchemaToZodShape.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,CAAC,MAAM,KAAK,CAAC;AAEzB,MAAM,MAAM,kBAAkB,GAAG;IAC/B,IAAI,EAAE,QAAQ,GAAG,QAAQ,GAAG,SAAS,GAAG,QAAQ,GAAG,OAAO,CAAC;IAC3D,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,KAAK,CAAC,EAAE,kBAAkB,CAAC;CAC5B,CAAC;AAEF,MAAM,MAAM,UAAU,GAAG;IACvB,IAAI,EAAE,QAAQ,CAAC;IACf,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,kBAAkB,CAAC,CAAC;IAChD,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;CACrB,CAAC;AA8CF,eAAO,MAAM,oBAAoB,GAC/B,QAAQ,UAAU,KACjB,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,UAAU,CAY7B,CAAC;AAEF,MAAM,WAAW,eAAe;IAE9B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,yBAAyB,GACpC,QAAQ,UAAU,EAClB,OAAO,OAAO,KACb,eAAe,EAOjB,CAAC"}
1
+ {"version":3,"file":"jsonSchemaToZodShape.d.ts","sourceRoot":"","sources":["../src/jsonSchemaToZodShape.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,CAAC,MAAM,KAAK,CAAC;AAEzB,MAAM,MAAM,kBAAkB,GAAG;IAC/B,IAAI,EAAE,QAAQ,GAAG,QAAQ,GAAG,SAAS,GAAG,SAAS,GAAG,QAAQ,GAAG,OAAO,CAAC;IACvE,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,KAAK,CAAC,EAAE,kBAAkB,CAAC;IAC3B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,kBAAkB,CAAC,CAAC;IAChD,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;CACrB,CAAC;AAEF,MAAM,MAAM,UAAU,GAAG;IACvB,IAAI,EAAE,QAAQ,CAAC;IACf,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,kBAAkB,CAAC,CAAC;IAChD,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;CACrB,CAAC;AAuEF,eAAO,MAAM,oBAAoB,GAC/B,QAAQ,UAAU,KACjB,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,UAAU,CACe,CAAC;AAE9C,MAAM,WAAW,eAAe;IAE9B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,yBAAyB,GACpC,QAAQ,UAAU,EAClB,OAAO,OAAO,KACb,eAAe,EAOjB,CAAC"}
@@ -54,8 +54,9 @@ const propertyToZod = (prop) => {
54
54
  }
55
55
  break;
56
56
  }
57
- case 'number': {
58
- let num = z.number();
57
+ case 'number':
58
+ case 'integer': {
59
+ let num = prop.type === 'integer' ? z.number().int() : z.number();
59
60
  if (prop.minimum !== undefined)
60
61
  num = num.min(prop.minimum);
61
62
  if (prop.maximum !== undefined)
@@ -66,11 +67,23 @@ const propertyToZod = (prop) => {
66
67
  case 'boolean':
67
68
  field = z.boolean();
68
69
  break;
69
- case 'array':
70
- field = z.array(prop.items ? propertyToZod(prop.items) : z.any());
70
+ case 'array': {
71
+ let arr = z.array(prop.items ? propertyToZod(prop.items) : z.any());
72
+ if (prop.minItems !== undefined)
73
+ arr = arr.min(prop.minItems);
74
+ if (prop.maxItems !== undefined)
75
+ arr = arr.max(prop.maxItems);
76
+ field = arr;
71
77
  break;
78
+ }
72
79
  case 'object':
73
- field = z.record(z.string(), z.any());
80
+ // With declared properties, keep them: this shape is also what
81
+ // `tools/list` advertises, and a bare record there tells the model an
82
+ // item is any object at all, so it invents the field names. Loose,
83
+ // because a schema without `additionalProperties: false` allows extras.
84
+ field = prop.properties
85
+ ? z.looseObject(shapeOf(prop.properties, prop.required))
86
+ : z.record(z.string(), z.any());
74
87
  break;
75
88
  default:
76
89
  field = z.any();
@@ -80,16 +93,15 @@ const propertyToZod = (prop) => {
80
93
  }
81
94
  return field;
82
95
  };
83
- const jsonSchemaToZodShape = (schema) => {
84
- const properties = schema.properties ?? {};
85
- const required = schema.required ?? [];
96
+ function shapeOf(properties = {}, required = []) {
86
97
  const shape = {};
87
98
  for (const [key, prop] of Object.entries(properties)) {
88
99
  const field = propertyToZod(prop);
89
100
  shape[key] = required.includes(key) ? field : field.optional();
90
101
  }
91
102
  return shape;
92
- };
103
+ }
104
+ const jsonSchemaToZodShape = (schema) => shapeOf(schema.properties, schema.required);
93
105
  exports.jsonSchemaToZodShape = jsonSchemaToZodShape;
94
106
  /**
95
107
  * Check a value against a declared JSON schema, returning the violations.
@@ -0,0 +1,47 @@
1
+ /**
2
+ * The cursor for the next page a JSON result names at its top level, if any.
3
+ * Cut text can lose it, so it is read from the parsed value before cutting.
4
+ */
5
+ export declare const findNextCursor: (value: unknown) => string | undefined;
6
+ /**
7
+ * `text` within `maxBytes` of UTF-8, or its start with a note that says it was
8
+ * cut. A character split by the cut is dropped rather than shown broken.
9
+ * `next` names the result's own cursor, for "call again with it".
10
+ */
11
+ export declare const capText: (text: string, maxBytes: number, next?: string) => string;
12
+ export declare class InvalidCursorError extends Error {
13
+ constructor();
14
+ }
15
+ /**
16
+ * What `list-resources` answers, wherever it runs (the MCP worker, and the
17
+ * channel runner, which lists proxied resources too): one page by uri, how many
18
+ * there are in all, and the cursor for the next page when there is one.
19
+ */
20
+ export declare const listResourcesPage: (resources: Array<{
21
+ uri: string;
22
+ title?: string | null;
23
+ description?: string | null;
24
+ mimeType?: string | null;
25
+ }>, args: {
26
+ cursor?: unknown;
27
+ limit?: unknown;
28
+ }, limits: {
29
+ defaultLimit: number;
30
+ maxLimit: number;
31
+ }) => string;
32
+ /**
33
+ * One page of `items`, ordered by `keyOf`, starting after the item the cursor
34
+ * names. Keyed rather than counted, so an item added or removed between calls
35
+ * doesn't shift the next page onto ones already seen.
36
+ */
37
+ export declare const paginate: <T>(items: T[], keyOf: (item: T) => string, options: {
38
+ cursor?: string;
39
+ limit?: number;
40
+ defaultLimit: number;
41
+ maxLimit: number;
42
+ }) => {
43
+ page: T[];
44
+ nextCursor?: string;
45
+ total: number;
46
+ };
47
+ //# sourceMappingURL=pagination.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pagination.d.ts","sourceRoot":"","sources":["../src/pagination.ts"],"names":[],"mappings":"AAkBA;;;GAGG;AACH,eAAO,MAAM,cAAc,GAAI,OAAO,OAAO,KAAG,MAAM,GAAG,SAUxD,CAAC;AAIF;;;;GAIG;AACH,eAAO,MAAM,OAAO,GAClB,MAAM,MAAM,EACZ,UAAU,MAAM,EAChB,OAAO,MAAM,KACZ,MAaF,CAAC;AAmCF,qBAAa,kBAAmB,SAAQ,KAAK;;CAO5C;AAED;;;;GAIG;AACH,eAAO,MAAM,iBAAiB,GAC5B,WAAW,KAAK,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B,CAAC,EACF,MAAM;IAAE,MAAM,CAAC,EAAE,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE,OAAO,CAAA;CAAE,EAC3C,QAAQ;IAAE,YAAY,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,KACjD,MAgBF,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,QAAQ,GAAI,CAAC,EACxB,OAAO,CAAC,EAAE,EACV,OAAO,CAAC,IAAI,EAAE,CAAC,KAAK,MAAM,EAC1B,SAAS;IACP,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,MAAM,CAAC;CAClB,KACA;IAAE,IAAI,EAAE,CAAC,EAAE,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CA4CjD,CAAC"}
@@ -0,0 +1,160 @@
1
+ "use strict";
2
+ // Large tool results, in one shape: a list a tool owns is paged by a cursor the
3
+ // model passes back, and text that has to be cut says so, how much was left
4
+ // out, and where to continue when the result itself carried a cursor.
5
+ //
6
+ // Imports nothing, so node --test runs it directly.
7
+ Object.defineProperty(exports, "__esModule", { value: true });
8
+ exports.paginate = exports.listResourcesPage = exports.InvalidCursorError = exports.capText = exports.findNextCursor = void 0;
9
+ // Keys a paged API commonly answers with. Read at the top level only: deeper
10
+ // than that, a key with one of these names is somebody's data.
11
+ const NEXT_CURSOR_KEYS = [
12
+ 'nextCursor',
13
+ 'next_cursor',
14
+ 'nextPageToken',
15
+ 'next_page_token',
16
+ 'nextPage',
17
+ 'next_page',
18
+ 'next'
19
+ ];
20
+ /**
21
+ * The cursor for the next page a JSON result names at its top level, if any.
22
+ * Cut text can lose it, so it is read from the parsed value before cutting.
23
+ */
24
+ const findNextCursor = (value) => {
25
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
26
+ return undefined;
27
+ }
28
+ for (const key of NEXT_CURSOR_KEYS) {
29
+ const found = value[key];
30
+ if (typeof found === 'string' && found)
31
+ return `${key}: ${found}`;
32
+ if (typeof found === 'number')
33
+ return `${key}: ${found}`;
34
+ }
35
+ return undefined;
36
+ };
37
+ exports.findNextCursor = findNextCursor;
38
+ const kb = (bytes) => `${Math.max(1, Math.round(bytes / 1024))} KB`;
39
+ /**
40
+ * `text` within `maxBytes` of UTF-8, or its start with a note that says it was
41
+ * cut. A character split by the cut is dropped rather than shown broken.
42
+ * `next` names the result's own cursor, for "call again with it".
43
+ */
44
+ const capText = (text, maxBytes, next) => {
45
+ const bytes = new TextEncoder().encode(text);
46
+ if (bytes.byteLength <= maxBytes)
47
+ return text;
48
+ const kept = new TextDecoder()
49
+ .decode(bytes.slice(0, maxBytes))
50
+ .replace(/�+$/, '');
51
+ const continueWith = next
52
+ ? ` The result names its next page (${next}): call again with it to continue.`
53
+ : ' To see the rest, call again asking for less — narrower filters, a smaller page or fewer fields.';
54
+ return `${kept}\n\n[Truncated: showing the first ${kb(maxBytes)} of ${kb(bytes.byteLength)}.${continueWith}]`;
55
+ };
56
+ exports.capText = capText;
57
+ // Opaque to the model, so it is passed back as given rather than edited.
58
+ const encodeCursor = (key) => btoa(String.fromCharCode(...new TextEncoder().encode(key)))
59
+ .replace(/\+/g, '-')
60
+ .replace(/\//g, '_')
61
+ .replace(/=+$/, '');
62
+ const decodeCursor = (cursor) => {
63
+ try {
64
+ const base64 = cursor.replace(/-/g, '+').replace(/_/g, '/');
65
+ const binary = atob(base64);
66
+ return new TextDecoder('utf-8', { fatal: true }).decode(Uint8Array.from(binary, char => char.charCodeAt(0)));
67
+ }
68
+ catch {
69
+ return null;
70
+ }
71
+ };
72
+ // Between a cursor's key and the count of items with that key already served.
73
+ // A key is a uri or a name, never a NUL.
74
+ const CURSOR_SEEN_SEPARATOR = '\u0000';
75
+ // A cursor with no count (one issued before the count was added) resumes
76
+ // after every item with its key.
77
+ const splitCursorKey = (decoded) => {
78
+ const at = decoded.lastIndexOf(CURSOR_SEEN_SEPARATOR);
79
+ if (at === -1)
80
+ return { key: decoded, seen: Infinity };
81
+ const count = decoded.slice(at + 1);
82
+ if (!/^\d+$/.test(count))
83
+ throw new InvalidCursorError();
84
+ return { key: decoded.slice(0, at), seen: Number(count) };
85
+ };
86
+ class InvalidCursorError extends Error {
87
+ constructor() {
88
+ super('That cursor is not valid. Call again without a cursor to start from the first page.');
89
+ this.name = 'InvalidCursorError';
90
+ }
91
+ }
92
+ exports.InvalidCursorError = InvalidCursorError;
93
+ /**
94
+ * What `list-resources` answers, wherever it runs (the MCP worker, and the
95
+ * channel runner, which lists proxied resources too): one page by uri, how many
96
+ * there are in all, and the cursor for the next page when there is one.
97
+ */
98
+ const listResourcesPage = (resources, args, limits) => {
99
+ const { page, total, nextCursor } = (0, exports.paginate)(resources, r => r.uri, {
100
+ cursor: typeof args.cursor === 'string' ? args.cursor : undefined,
101
+ limit: typeof args.limit === 'number' ? args.limit : undefined,
102
+ ...limits
103
+ });
104
+ return JSON.stringify({
105
+ resources: page.map(r => ({
106
+ uri: r.uri,
107
+ title: r.title || undefined,
108
+ description: r.description || undefined,
109
+ mimeType: r.mimeType || undefined
110
+ })),
111
+ total,
112
+ ...(nextCursor ? { nextCursor } : {})
113
+ });
114
+ };
115
+ exports.listResourcesPage = listResourcesPage;
116
+ /**
117
+ * One page of `items`, ordered by `keyOf`, starting after the item the cursor
118
+ * names. Keyed rather than counted, so an item added or removed between calls
119
+ * doesn't shift the next page onto ones already seen.
120
+ */
121
+ const paginate = (items, keyOf, options) => {
122
+ const limit = Math.min(Math.max(Math.floor(Number(options.limit) || options.defaultLimit), 1), options.maxLimit);
123
+ const sorted = [...items].sort((a, b) => {
124
+ const left = keyOf(a);
125
+ const right = keyOf(b);
126
+ return left < right ? -1 : left > right ? 1 : 0;
127
+ });
128
+ let start = 0;
129
+ if (options.cursor) {
130
+ const decoded = decodeCursor(options.cursor);
131
+ if (decoded === null)
132
+ throw new InvalidCursorError();
133
+ const { key: after, seen } = splitCursorKey(decoded);
134
+ start = sorted.findIndex(item => keyOf(item) >= after);
135
+ if (start === -1)
136
+ start = sorted.length;
137
+ // Items that share the cursor's key: skip the ones already served, in the
138
+ // order the sort (which is stable) keeps them.
139
+ for (let skipped = 0; start < sorted.length && keyOf(sorted[start]) === after && skipped < seen; skipped++) {
140
+ start++;
141
+ }
142
+ }
143
+ const page = sorted.slice(start, start + limit);
144
+ const more = start + limit < sorted.length;
145
+ if (!more || page.length === 0)
146
+ return { page, total: sorted.length };
147
+ // The last key served, and how many items with that key have been served
148
+ // so far, so a run of equal keys split across pages resumes inside the run.
149
+ const end = start + page.length;
150
+ const lastKey = keyOf(page[page.length - 1]);
151
+ let seen = 0;
152
+ for (let i = end - 1; i >= 0 && keyOf(sorted[i]) === lastKey; i--)
153
+ seen++;
154
+ return {
155
+ page,
156
+ total: sorted.length,
157
+ nextCursor: encodeCursor(`${lastKey}${CURSOR_SEEN_SEPARATOR}${seen}`)
158
+ };
159
+ };
160
+ exports.paginate = paginate;
@@ -1,5 +1,6 @@
1
1
  export interface QueueMessageLike<T> {
2
2
  body: T;
3
+ attempts?: number;
3
4
  ack: () => void;
4
5
  retry: (opts?: {
5
6
  delaySeconds?: number;
@@ -9,9 +10,13 @@ export interface QueueBatchLike<T> {
9
10
  queue: string;
10
11
  messages: readonly QueueMessageLike<T>[];
11
12
  }
13
+ export interface QueueFailureContext {
14
+ final: boolean;
15
+ }
12
16
  export interface ProcessQueueBatchHandlers<T> {
13
17
  process: (body: T) => Promise<void>;
14
- onError: (error: unknown, body: T, queueName: string) => Promise<void>;
18
+ onError: (error: unknown, body: T, queueName: string, context: QueueFailureContext) => Promise<void>;
19
+ maxRetries?: number;
15
20
  }
16
21
  export declare const processQueueBatch: <T>(batch: QueueBatchLike<T>, handlers: ProcessQueueBatchHandlers<T>) => Promise<void>;
17
22
  //# sourceMappingURL=processQueueBatch.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"processQueueBatch.d.ts","sourceRoot":"","sources":["../src/processQueueBatch.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,gBAAgB,CAAC,CAAC;IACjC,IAAI,EAAE,CAAC,CAAC;IACR,GAAG,EAAE,MAAM,IAAI,CAAC;IAChB,KAAK,EAAE,CAAC,IAAI,CAAC,EAAE;QAAE,YAAY,CAAC,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC;CACnD;AAED,MAAM,WAAW,cAAc,CAAC,CAAC;IAC/B,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,SAAS,gBAAgB,CAAC,CAAC,CAAC,EAAE,CAAC;CAC1C;AAED,MAAM,WAAW,yBAAyB,CAAC,CAAC;IAC1C,OAAO,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACpC,OAAO,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,EAAE,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CACxE;AAED,eAAO,MAAM,iBAAiB,GAAU,CAAC,EACvC,OAAO,cAAc,CAAC,CAAC,CAAC,EACxB,UAAU,yBAAyB,CAAC,CAAC,CAAC,KACrC,OAAO,CAAC,IAAI,CAuBd,CAAC"}
1
+ {"version":3,"file":"processQueueBatch.d.ts","sourceRoot":"","sources":["../src/processQueueBatch.ts"],"names":[],"mappings":"AAKA,MAAM,WAAW,gBAAgB,CAAC,CAAC;IACjC,IAAI,EAAE,CAAC,CAAC;IAER,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,GAAG,EAAE,MAAM,IAAI,CAAC;IAChB,KAAK,EAAE,CAAC,IAAI,CAAC,EAAE;QAAE,YAAY,CAAC,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC;CACnD;AAED,MAAM,WAAW,cAAc,CAAC,CAAC;IAC/B,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,SAAS,gBAAgB,CAAC,CAAC,CAAC,EAAE,CAAC;CAC1C;AAED,MAAM,WAAW,mBAAmB;IAIlC,KAAK,EAAE,OAAO,CAAC;CAChB;AAED,MAAM,WAAW,yBAAyB,CAAC,CAAC;IAC1C,OAAO,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACpC,OAAO,EAAE,CACP,KAAK,EAAE,OAAO,EACd,IAAI,EAAE,CAAC,EACP,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,mBAAmB,KACzB,OAAO,CAAC,IAAI,CAAC,CAAC;IAEnB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AASD,eAAO,MAAM,iBAAiB,GAAU,CAAC,EACvC,OAAO,cAAc,CAAC,CAAC,CAAC,EACxB,UAAU,yBAAyB,CAAC,CAAC,CAAC,KACrC,OAAO,CAAC,IAAI,CA4Bd,CAAC"}
@@ -3,25 +3,38 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.processQueueBatch = void 0;
4
4
  const retry_1 = require("./retry");
5
5
  const constants_1 = require("./constants");
6
+ const queueFailure_1 = require("./queueFailure");
7
+ const failureKind = (error) => {
8
+ if ((0, queueFailure_1.isPermanentJobError)(error))
9
+ return 'permanent';
10
+ if ((0, retry_1.isTransientInfraError)(error))
11
+ return 'transient';
12
+ if ((0, retry_1.isRateLimitError)(error))
13
+ return 'rateLimit';
14
+ return 'other';
15
+ };
6
16
  const processQueueBatch = async (batch, handlers) => {
17
+ const maxRetries = handlers.maxRetries ?? constants_1.constants.QUEUE_MAX_RETRIES;
7
18
  for (const message of batch.messages) {
8
19
  try {
9
20
  await handlers.process(message.body);
10
21
  message.ack();
11
22
  }
12
23
  catch (error) {
13
- if ((0, retry_1.isTransientInfraError)(error)) {
14
- // Transient Cloudflare platform hiccup (e.g. a Durable Object /
15
- // Container reset). The job itself is fine, so re-run it after a short
16
- // backoff without reporting an error or marking the work failed.
24
+ const plan = (0, queueFailure_1.planQueueFailure)(failureKind(error), message.attempts, maxRetries);
25
+ if (plan.report) {
26
+ await handlers.onError(error, message.body, batch.queue, {
27
+ final: plan.final
28
+ });
29
+ }
30
+ if (plan.action === 'ack') {
31
+ message.ack();
32
+ }
33
+ else if (plan.backoff === 'transient') {
17
34
  message.retry({ delaySeconds: constants_1.constants.TRANSIENT_BACKOFF_SECONDS });
18
- continue;
19
35
  }
20
- await handlers.onError(error, message.body, batch.queue);
21
- if ((0, retry_1.isRateLimitError)(error)) {
22
- message.retry({
23
- delaySeconds: constants_1.constants.RATE_LIMIT_BACKOFF_SECONDS
24
- });
36
+ else if (plan.backoff === 'rateLimit') {
37
+ message.retry({ delaySeconds: constants_1.constants.RATE_LIMIT_BACKOFF_SECONDS });
25
38
  }
26
39
  else {
27
40
  message.retry();
@@ -0,0 +1,14 @@
1
+ export declare class PermanentJobError extends Error {
2
+ readonly permanent = true;
3
+ constructor(message: string);
4
+ }
5
+ export declare const isPermanentJobError: (error: unknown) => boolean;
6
+ export type QueueFailureKind = 'permanent' | 'transient' | 'rateLimit' | 'other';
7
+ export interface QueueFailurePlan {
8
+ report: boolean;
9
+ final: boolean;
10
+ action: 'ack' | 'retry';
11
+ backoff: 'transient' | 'rateLimit' | null;
12
+ }
13
+ export declare const planQueueFailure: (kind: QueueFailureKind, attempts: number | undefined, maxRetries: number) => QueueFailurePlan;
14
+ //# sourceMappingURL=queueFailure.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"queueFailure.d.ts","sourceRoot":"","sources":["../src/queueFailure.ts"],"names":[],"mappings":"AASA,qBAAa,iBAAkB,SAAQ,KAAK;IAC1C,QAAQ,CAAC,SAAS,QAAQ;gBAEd,OAAO,EAAE,MAAM;CAI5B;AAED,eAAO,MAAM,mBAAmB,GAAI,OAAO,OAAO,KAAG,OAGE,CAAC;AAExD,MAAM,MAAM,gBAAgB,GACxB,WAAW,GACX,WAAW,GACX,WAAW,GACX,OAAO,CAAC;AAEZ,MAAM,WAAW,gBAAgB;IAE/B,MAAM,EAAE,OAAO,CAAC;IAEhB,KAAK,EAAE,OAAO,CAAC;IAEf,MAAM,EAAE,KAAK,GAAG,OAAO,CAAC;IAExB,OAAO,EAAE,WAAW,GAAG,WAAW,GAAG,IAAI,CAAC;CAC3C;AAID,eAAO,MAAM,gBAAgB,GAC3B,MAAM,gBAAgB,EACtB,UAAU,MAAM,GAAG,SAAS,EAC5B,YAAY,MAAM,KACjB,gBAgBF,CAAC"}
@@ -0,0 +1,43 @@
1
+ "use strict";
2
+ // How a failed queue job is handled: retried, given up on, or reported. Kept
3
+ // apart from processQueueBatch, which applies it, so the decision is testable
4
+ // on its own.
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.planQueueFailure = exports.isPermanentJobError = exports.PermanentJobError = void 0;
7
+ // A failure that no retry can fix — a credential that needs the user to
8
+ // reconnect, a resource that no longer exists. The job is reported once and
9
+ // acknowledged rather than retried into the dead-letter queue. Recognised by
10
+ // its `permanent` flag rather than its class, so an error built by another
11
+ // copy of this module still counts.
12
+ class PermanentJobError extends Error {
13
+ permanent = true;
14
+ constructor(message) {
15
+ super(message);
16
+ this.name = 'PermanentJobError';
17
+ }
18
+ }
19
+ exports.PermanentJobError = PermanentJobError;
20
+ const isPermanentJobError = (error) => !!error &&
21
+ typeof error === 'object' &&
22
+ error.permanent === true;
23
+ exports.isPermanentJobError = isPermanentJobError;
24
+ // `attempts` is the queue's own count, 1 on the first delivery; a consumer
25
+ // with `maxRetries` retries gets maxRetries + 1 deliveries in all.
26
+ const planQueueFailure = (kind, attempts, maxRetries) => {
27
+ if (kind === 'permanent') {
28
+ return { report: true, final: true, action: 'ack', backoff: null };
29
+ }
30
+ const final = (attempts ?? 1) > maxRetries;
31
+ if (kind === 'transient') {
32
+ // A platform hiccup is retried quietly; only the last one is worth a
33
+ // report, since nothing will run the job after it.
34
+ return { report: final, final, action: 'retry', backoff: 'transient' };
35
+ }
36
+ return {
37
+ report: true,
38
+ final,
39
+ action: 'retry',
40
+ backoff: kind === 'rateLimit' ? 'rateLimit' : null
41
+ };
42
+ };
43
+ exports.planQueueFailure = planQueueFailure;
@@ -1 +1 @@
1
- {"version":3,"file":"resourceRerank.d.ts","sourceRoot":"","sources":["../src/resourceRerank.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAI1C,MAAM,WAAW,eAAe;IAC9B,GAAG,CACD,KAAK,EAAE,MAAM,EACb,KAAK,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,KAAK,CAAC;YAAE,IAAI,EAAE,MAAM,CAAA;SAAE,CAAC,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,GAC1E,OAAO,CAAC,OAAO,CAAC,CAAC;CACrB;AAMD;;;;;;;GAOG;AACH,eAAO,MAAM,sBAAsB,GACjC,QAAQ,SAAS,KAEf,CAAC,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,KAAK,OAAO,CAAC,MAAM,EAAE,GAAG,IAAI,CAAC,CAAC,GAClE,SAyBH,CAAC"}
1
+ {"version":3,"file":"resourceRerank.d.ts","sourceRoot":"","sources":["../src/resourceRerank.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAI1C,MAAM,WAAW,eAAe;IAC9B,GAAG,CACD,KAAK,EAAE,MAAM,EACb,KAAK,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,KAAK,CAAC;YAAE,IAAI,EAAE,MAAM,CAAA;SAAE,CAAC,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,GAC1E,OAAO,CAAC,OAAO,CAAC,CAAC;CACrB;AAMD;;;;;;;GAOG;AACH,eAAO,MAAM,sBAAsB,GACjC,QAAQ,SAAS,KAEf,CAAC,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,KAAK,OAAO,CAAC,MAAM,EAAE,GAAG,IAAI,CAAC,CAAC,GAClE,SA6BH,CAAC"}
@@ -20,7 +20,9 @@ const createResourceReranker = (source) => {
20
20
  contexts: documents.map(text => ({ text })),
21
21
  top_k: documents.length
22
22
  }));
23
- const scores = new Array(documents.length);
23
+ // Filled, not just sized: `some` skips the holes of a bare new Array(n), so
24
+ // a document the response left out would pass the check below unscored.
25
+ const scores = new Array(documents.length).fill(undefined);
24
26
  for (const item of result?.response ?? []) {
25
27
  if (typeof item.id === 'number' &&
26
28
  typeof item.score === 'number' &&