@circuitwall/jarela 2.3.0 → 2.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.next/standalone/.next/BUILD_ID +1 -1
- package/.next/standalone/.next/app-path-routes-manifest.json +4 -4
- package/.next/standalone/.next/build-manifest.json +2 -2
- package/.next/standalone/.next/prerender-manifest.json +3 -3
- package/.next/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/_global-error.html +1 -1
- package/.next/standalone/.next/server/app/_global-error.rsc +1 -1
- package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/_not-found.html +2 -2
- package/.next/standalone/.next/server/app/_not-found.rsc +2 -2
- package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/api/v1/access/[identity]/route.js +7 -0
- package/.next/standalone/.next/server/app/api/v1/access/route.js +7 -0
- package/.next/standalone/.next/server/app/api/v1/agents/[id]/compact/route.js +7 -0
- package/.next/standalone/.next/server/app/api/v1/agents/[id]/display-filters/route.js +17 -5
- package/.next/standalone/.next/server/app/api/v1/agents/[id]/route.js +7 -0
- package/.next/standalone/.next/server/app/api/v1/agents/[id]/thread/route.js +17 -5
- package/.next/standalone/.next/server/app/api/v1/agents/route.js +7 -0
- package/.next/standalone/.next/server/app/api/v1/allowed-sites/[hostname]/route.js +7 -0
- package/.next/standalone/.next/server/app/api/v1/allowed-sites/route.js +7 -0
- package/.next/standalone/.next/server/app/api/v1/artifacts/lifecycle/route.js +7 -0
- package/.next/standalone/.next/server/app/api/v1/bridges/[id]/chats/route.js +26 -32
- package/.next/standalone/.next/server/app/api/v1/bridges/[id]/chats/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/v1/bridges/[id]/ignores/[remote_jid]/route.js +18 -7
- package/.next/standalone/.next/server/app/api/v1/bridges/[id]/ignores/route.js +18 -7
- package/.next/standalone/.next/server/app/api/v1/bridges/[id]/lookup/route.js +26 -32
- package/.next/standalone/.next/server/app/api/v1/bridges/[id]/lookup/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/v1/bridges/[id]/pair/route.js +27 -33
- package/.next/standalone/.next/server/app/api/v1/bridges/[id]/pair/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/v1/bridges/[id]/route.js +26 -32
- package/.next/standalone/.next/server/app/api/v1/bridges/[id]/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/v1/bridges/[id]/routes/[route_id]/route.js +28 -12
- package/.next/standalone/.next/server/app/api/v1/bridges/[id]/routes/route.js +28 -12
- package/.next/standalone/.next/server/app/api/v1/bridges/[id]/status/route.js +26 -32
- package/.next/standalone/.next/server/app/api/v1/bridges/[id]/status/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/v1/bridges/kinds/route.js +9 -2
- package/.next/standalone/.next/server/app/api/v1/bridges/kinds/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/v1/bridges/route.js +19 -7
- package/.next/standalone/.next/server/app/api/v1/bridges/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/v1/dashboard/metrics/route.js +17 -5
- package/.next/standalone/.next/server/app/api/v1/dashboard/pricing/route.js +7 -0
- package/.next/standalone/.next/server/app/api/v1/documents/search/route.js +7 -0
- package/.next/standalone/.next/server/app/api/v1/documents/settings/route.js +7 -0
- package/.next/standalone/.next/server/app/api/v1/env-sync/allowlist/route.js +7 -0
- package/.next/standalone/.next/server/app/api/v1/env-sync/route.js +7 -0
- package/.next/standalone/.next/server/app/api/v1/events/route.js +26 -32
- package/.next/standalone/.next/server/app/api/v1/events/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/v1/extension/browser/activate/route.js +7 -0
- package/.next/standalone/.next/server/app/api/v1/extension/browser/foreground/route.js +7 -0
- package/.next/standalone/.next/server/app/api/v1/extension/browser/history/route.js +7 -0
- package/.next/standalone/.next/server/app/api/v1/extension/browser/poll/route.js +7 -0
- package/.next/standalone/.next/server/app/api/v1/extension/browser/progress/route.js +7 -0
- package/.next/standalone/.next/server/app/api/v1/extension/browser/result/route.js +7 -0
- package/.next/standalone/.next/server/app/api/v1/extension/browser/retry/route.js +7 -0
- package/.next/standalone/.next/server/app/api/v1/extension/browser/status/route.js +7 -0
- package/.next/standalone/.next/server/app/api/v1/extension/browser/tabs/route.js +7 -0
- package/.next/standalone/.next/server/app/api/v1/harnesses/[id]/route.js +7 -0
- package/.next/standalone/.next/server/app/api/v1/harnesses/default/route.js +7 -0
- package/.next/standalone/.next/server/app/api/v1/harnesses/route.js +7 -0
- package/.next/standalone/.next/server/app/api/v1/health/route.js +17 -5
- package/.next/standalone/.next/server/app/api/v1/integrations/[name]/route.js +7 -0
- package/.next/standalone/.next/server/app/api/v1/integrations/gmail/oauth/callback/route.js +7 -0
- package/.next/standalone/.next/server/app/api/v1/integrations/gmail/oauth/start/route.js +7 -0
- package/.next/standalone/.next/server/app/api/v1/integrations/gmail/oauth/status/route.js +7 -0
- package/.next/standalone/.next/server/app/api/v1/integrations/outlook/oauth/callback/route.js +7 -0
- package/.next/standalone/.next/server/app/api/v1/integrations/outlook/oauth/start/route.js +7 -0
- package/.next/standalone/.next/server/app/api/v1/integrations/outlook/oauth/status/route.js +7 -0
- package/.next/standalone/.next/server/app/api/v1/integrations/route.js +7 -0
- package/.next/standalone/.next/server/app/api/v1/lifecycle/adoption/route.js +17 -5
- package/.next/standalone/.next/server/app/api/v1/maps/embed/route.js +7 -0
- package/.next/standalone/.next/server/app/api/v1/mcp-servers/[name]/route.js +17 -5
- package/.next/standalone/.next/server/app/api/v1/mcp-servers/route.js +17 -5
- package/.next/standalone/.next/server/app/api/v1/memory/[namespace]/[key]/route.js +7 -0
- package/.next/standalone/.next/server/app/api/v1/memory/policy/route.js +7 -0
- package/.next/standalone/.next/server/app/api/v1/memory/route.js +7 -0
- package/.next/standalone/.next/server/app/api/v1/memory/search/route.js +7 -0
- package/.next/standalone/.next/server/app/api/v1/models/[name]/compact-threads/route.js +7 -0
- package/.next/standalone/.next/server/app/api/v1/models/[name]/route.js +7 -0
- package/.next/standalone/.next/server/app/api/v1/models/route.js +7 -0
- package/.next/standalone/.next/server/app/api/v1/pending-actions/[id]/approve/route.js +27 -12
- package/.next/standalone/.next/server/app/api/v1/profile/location/route.js +7 -0
- package/.next/standalone/.next/server/app/api/v1/profile/route.js +7 -0
- package/.next/standalone/.next/server/app/api/v1/providers/[provider]/models/route.js +7 -0
- package/.next/standalone/.next/server/app/api/v1/providers/[provider]/probe/route.js +7 -0
- package/.next/standalone/.next/server/app/api/v1/providers/github-copilot/auth/route.js +7 -0
- package/.next/standalone/.next/server/app/api/v1/providers/route.js +7 -0
- package/.next/standalone/.next/server/app/api/v1/proxy-config/route.js +7 -0
- package/.next/standalone/.next/server/app/api/v1/redaction/init/route.js +7 -0
- package/.next/standalone/.next/server/app/api/v1/redaction/route.js +7 -0
- package/.next/standalone/.next/server/app/api/v1/scheduled-tasks/[id]/route.js +17 -5
- package/.next/standalone/.next/server/app/api/v1/scheduled-tasks/route.js +7 -0
- package/.next/standalone/.next/server/app/api/v1/security/idle-timeout/route.js +7 -0
- package/.next/standalone/.next/server/app/api/v1/security/pin/route.js +7 -0
- package/.next/standalone/.next/server/app/api/v1/security/state/route.js +7 -0
- package/.next/standalone/.next/server/app/api/v1/security/unlock/route.js +7 -0
- package/.next/standalone/.next/server/app/api/v1/security/verify-pin/route.js +7 -0
- package/.next/standalone/.next/server/app/api/v1/skills/[id]/route.js +7 -0
- package/.next/standalone/.next/server/app/api/v1/skills/repos/[id]/route.js +7 -0
- package/.next/standalone/.next/server/app/api/v1/skills/repos/route.js +7 -0
- package/.next/standalone/.next/server/app/api/v1/skills/route.js +7 -0
- package/.next/standalone/.next/server/app/api/v1/tasks/[agent_id]/route.js +7 -0
- package/.next/standalone/.next/server/app/api/v1/tasks/route.js +7 -0
- package/.next/standalone/.next/server/app/api/v1/threads/[thread_id]/activity/route.js +7 -0
- package/.next/standalone/.next/server/app/api/v1/threads/[thread_id]/context-pin/route.js +12 -6
- package/.next/standalone/.next/server/app/api/v1/threads/[thread_id]/route.js +7 -0
- package/.next/standalone/.next/server/app/api/v1/threads/[thread_id]/run/route.js +0 -1
- package/.next/standalone/.next/server/app/api/v1/threads/route.js +17 -5
- package/.next/standalone/.next/server/app/api/v1/update/apply/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/v1/voice/transcribe/route.js +17 -5
- package/.next/standalone/.next/server/app/api/v1/voice/tts/route.js +17 -5
- package/.next/standalone/.next/server/app/api/v1/watchers/[id]/route.js +17 -5
- package/.next/standalone/.next/server/app/page.js +322 -275
- package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/setup/page.js +8 -0
- package/.next/standalone/.next/server/app/setup/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app-paths-manifest.json +4 -4
- package/.next/standalone/.next/server/chunks/1388.js +106 -36
- package/.next/standalone/.next/server/chunks/2068.js +47 -10
- package/.next/standalone/.next/server/chunks/210.js +6 -6
- package/.next/standalone/.next/server/chunks/2447.js +10 -5
- package/.next/standalone/.next/server/chunks/2471.js +17 -17
- package/.next/standalone/.next/server/chunks/3042.js +10 -8
- package/.next/standalone/.next/server/chunks/{4540.js → 309.js} +104 -38
- package/.next/standalone/.next/server/chunks/{2350.js → 3272.js} +104 -37
- package/.next/standalone/.next/server/chunks/3399.js +525 -525
- package/.next/standalone/.next/server/chunks/3524.js +150 -67
- package/.next/standalone/.next/server/chunks/4189.js +1 -1
- package/.next/standalone/.next/server/chunks/4508.js +2 -2
- package/.next/standalone/.next/server/chunks/46.js +73 -28
- package/.next/standalone/.next/server/chunks/5908.js +63 -42
- package/.next/standalone/.next/server/chunks/6142.js +10 -2
- package/.next/standalone/.next/server/chunks/6649.js +1 -1
- package/.next/standalone/.next/server/chunks/8461.js +10 -8
- package/.next/standalone/.next/server/chunks/9032.js +5 -5
- package/.next/standalone/.next/server/chunks/9164.js +435 -176
- package/.next/standalone/.next/server/chunks/9304.js +51 -12
- package/.next/standalone/.next/server/chunks/{423.js → 9590.js} +66 -21
- package/.next/standalone/.next/server/middleware-build-manifest.js +2 -2
- package/.next/standalone/.next/server/middleware.js +71 -18
- package/.next/standalone/.next/server/pages/404.html +2 -2
- package/.next/standalone/.next/server/pages/500.html +1 -1
- package/.next/standalone/.next/server/preview-props.json +3 -3
- package/.next/standalone/.next/server/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/static/chunks/app/{page-213a623b37226452.js → page-04748847c6ff866f.js} +171 -132
- package/.next/standalone/.next/static/css/{312542a5725ba0d5.css → 2669e8744cc8c212.css} +2 -2
- package/.next/standalone/docs/adoption/changesets.json +49 -0
- package/.next/standalone/package.json +1 -1
- package/CHANGELOG.md +19 -0
- package/app/api/v1/threads/[thread_id]/run/route.ts +0 -1
- package/components/chat/MessageBubble.tsx +12 -0
- package/components/integrations/NetworkSection.tsx +2 -2
- package/lib/agents/citation-checker.test.ts +40 -2
- package/lib/agents/citation-checker.ts +56 -11
- package/lib/agents/context-boundary.ts +5 -6
- package/lib/agents/instruction-edits.test.ts +120 -0
- package/lib/agents/instruction-edits.ts +65 -42
- package/lib/agents/prepare/request.ts +0 -2
- package/lib/agents/prepare/system-prompt.test.ts +10 -0
- package/lib/agents/prepare/system-prompt.ts +8 -2
- package/lib/agents/proposals.test.ts +76 -0
- package/lib/agents/proposals.ts +13 -5
- package/lib/agents/run-thread.auto-boundary.test.ts +125 -5
- package/lib/agents/run-thread.ts +24 -9
- package/lib/agents/sqlite-checkpoint-saver.test.ts +91 -0
- package/lib/agents/warm-summary-background.ts +22 -2
- package/lib/agents/warm-topics.test.ts +155 -1
- package/lib/agents/warm-topics.ts +27 -7
- package/lib/bridges/dispatcher.ts +1 -0
- package/lib/bridges/message-role.test.ts +81 -0
- package/lib/bridges/message-role.ts +23 -10
- package/lib/bridges/types.ts +6 -0
- package/lib/bridges/whatsapp-link.test.ts +32 -0
- package/lib/bridges/whatsapp-link.ts +21 -0
- package/lib/bridges/whatsapp.inbound.test.ts +239 -0
- package/lib/bridges/whatsapp.sender.test.ts +80 -1
- package/lib/bridges/whatsapp.ts +76 -38
- package/lib/pricing/snapshot.test.ts +70 -0
- package/lib/pricing/snapshot.ts +10 -2
- package/lib/proxy/dispatcher.test.ts +119 -1
- package/lib/proxy/dispatcher.ts +22 -9
- package/lib/stores/agent-configs.ts +10 -5
- package/lib/stores/bridges.test.ts +81 -0
- package/lib/stores/bridges.ts +9 -8
- package/lib/stores/proxy-config.test.ts +120 -0
- package/lib/stores/proxy-config.ts +22 -4
- package/lib/stores/threads.ts +12 -8
- package/lib/tools/communications/calendar.test.ts +43 -2
- package/lib/tools/communications/calendar.ts +2 -0
- package/lib/tools/communications/gmail.test.ts +51 -1
- package/lib/tools/communications/gmail.ts +44 -2
- package/lib/tools/communications/outlook-calendar.ts +2 -0
- package/lib/tools/communications/outlook.test.ts +33 -2
- package/lib/tools/communications/outlook.ts +2 -0
- package/lib/tools/filesystem/edit-match.test.ts +133 -0
- package/lib/tools/filesystem/edit-match.ts +7 -2
- package/lib/tools/support/result-refs.test.ts +131 -0
- package/lib/tools/support/result-refs.ts +41 -9
- package/lib/utils/source-link.test.ts +51 -0
- package/lib/utils/source-link.ts +45 -0
- package/package.json +1 -1
- /package/.next/standalone/.next/static/{bMRK5y3ruT7c3WtBfCQOG → hwujW4hR1b-r3k5WdMxZO}/_buildManifest.js +0 -0
- /package/.next/standalone/.next/static/{bMRK5y3ruT7c3WtBfCQOG → hwujW4hR1b-r3k5WdMxZO}/_ssgManifest.js +0 -0
|
@@ -93,23 +93,27 @@ export async function readToolResultRef(args: {
|
|
|
93
93
|
}
|
|
94
94
|
const offset = Math.max(0, Math.floor(args.offset ?? 0));
|
|
95
95
|
const limit = Math.min(MAX_READ_BYTES, Math.max(1, Math.floor(args.limit ?? DEFAULT_PREVIEW_BYTES)));
|
|
96
|
-
let buf: Buffer;
|
|
97
96
|
try {
|
|
98
97
|
const file = await fsp.open(abs, "r");
|
|
99
98
|
try {
|
|
100
99
|
const stat = await file.stat();
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
const
|
|
100
|
+
// Read a few bytes either side so the window can be moved onto UTF-8
|
|
101
|
+
// character boundaries; paging must never split a multi-byte character.
|
|
102
|
+
const windowStart = Math.min(Math.max(0, offset - 3), stat.size);
|
|
103
|
+
const windowLength = Math.max(0, Math.min(stat.size - windowStart, offset - windowStart + limit + 3));
|
|
104
|
+
const buf = Buffer.alloc(windowLength);
|
|
105
|
+
if (windowLength > 0) await file.read(buf, 0, windowLength, windowStart);
|
|
106
|
+
const { start, end } = utf8Window(buf, Math.min(offset, stat.size) - windowStart, limit);
|
|
107
|
+
const actualOffset = windowStart + start;
|
|
108
|
+
const nextOffset = windowStart + end;
|
|
105
109
|
return {
|
|
106
110
|
ok: true,
|
|
107
111
|
status: "done",
|
|
108
112
|
name: args.name,
|
|
109
|
-
offset,
|
|
113
|
+
offset: actualOffset,
|
|
110
114
|
limit,
|
|
111
115
|
bytes: stat.size,
|
|
112
|
-
result: buf.toString("utf8"),
|
|
116
|
+
result: buf.subarray(start, end).toString("utf8"),
|
|
113
117
|
next_offset: nextOffset < stat.size ? nextOffset : null,
|
|
114
118
|
done: nextOffset >= stat.size,
|
|
115
119
|
};
|
|
@@ -126,6 +130,25 @@ export async function readToolResultRef(args: {
|
|
|
126
130
|
}
|
|
127
131
|
}
|
|
128
132
|
|
|
133
|
+
const isUtf8Continuation = (byte: number | undefined) => byte !== undefined && (byte & 0xc0) === 0x80;
|
|
134
|
+
|
|
135
|
+
// Moves `[start, start + limit)` inside `buf` onto character boundaries: the
|
|
136
|
+
// start backs up to its lead byte, and the end backs up so it does not cut a
|
|
137
|
+
// character — unless that character alone exceeds `limit`, in which case the
|
|
138
|
+
// whole character is returned so paging always makes progress.
|
|
139
|
+
function utf8Window(buf: Buffer, start: number, limit: number): { start: number; end: number } {
|
|
140
|
+
let s = Math.max(0, Math.min(start, buf.length));
|
|
141
|
+
while (s > 0 && isUtf8Continuation(buf[s])) s--;
|
|
142
|
+
let e = Math.min(buf.length, s + limit);
|
|
143
|
+
if (e < buf.length && isUtf8Continuation(buf[e])) {
|
|
144
|
+
let lead = e;
|
|
145
|
+
while (lead > s && isUtf8Continuation(buf[lead])) lead--;
|
|
146
|
+
if (lead > s) e = lead;
|
|
147
|
+
else while (e < buf.length && isUtf8Continuation(buf[e])) e++;
|
|
148
|
+
}
|
|
149
|
+
return { start: s, end: e };
|
|
150
|
+
}
|
|
151
|
+
|
|
129
152
|
export function serializeToolResult(result: unknown): string {
|
|
130
153
|
if (typeof result === "string") return result;
|
|
131
154
|
return JSON.stringify(result) ?? String(result);
|
|
@@ -145,8 +168,15 @@ function buildPreview(value: string, mimeType: string): { value: unknown; bytes:
|
|
|
145
168
|
try {
|
|
146
169
|
const parsed = JSON.parse(value) as unknown;
|
|
147
170
|
if (Array.isArray(parsed)) {
|
|
148
|
-
|
|
149
|
-
|
|
171
|
+
// Cap by bytes as well as items: a few huge rows must not smuggle the
|
|
172
|
+
// whole result back inline.
|
|
173
|
+
for (let n = Math.min(parsed.length, JSON_ARRAY_PREVIEW_ITEMS); n > 0; n--) {
|
|
174
|
+
const preview = parsed.slice(0, n);
|
|
175
|
+
const bytes = Buffer.byteLength(JSON.stringify(preview));
|
|
176
|
+
if (bytes <= DEFAULT_PREVIEW_BYTES) return { value: preview, bytes, total_count: parsed.length };
|
|
177
|
+
}
|
|
178
|
+
const text = takeUtf8Prefix(value, DEFAULT_PREVIEW_BYTES);
|
|
179
|
+
return { value: text, bytes: Buffer.byteLength(text), total_count: parsed.length };
|
|
150
180
|
}
|
|
151
181
|
} catch {
|
|
152
182
|
// Fall through to byte preview.
|
|
@@ -165,6 +195,8 @@ function takeUtf8Prefix(value: string, maxBytes: number): string {
|
|
|
165
195
|
if (Buffer.byteLength(value.slice(0, mid)) <= maxBytes) lo = mid;
|
|
166
196
|
else hi = mid - 1;
|
|
167
197
|
}
|
|
198
|
+
const code = value.charCodeAt(lo - 1);
|
|
199
|
+
if (lo > 0 && code >= 0xd800 && code <= 0xdbff) lo--;
|
|
168
200
|
return value.slice(0, lo);
|
|
169
201
|
}
|
|
170
202
|
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { citeAs, httpsUrlOrNull } from "./source-link";
|
|
3
|
+
|
|
4
|
+
describe("httpsUrlOrNull", () => {
|
|
5
|
+
it("accepts absolute https URLs", () => {
|
|
6
|
+
expect(httpsUrlOrNull("https://outlook.office365.com/owa/?ItemID=AAk%3D&exvsurl=1")).toBe(
|
|
7
|
+
"https://outlook.office365.com/owa/?ItemID=AAk%3D&exvsurl=1",
|
|
8
|
+
);
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
it("rejects missing, relative, and non-https URLs", () => {
|
|
12
|
+
for (const url of [null, undefined, "", "/mail/123", "http://example.com", "javascript:alert(1)", "message://%3cid%3e", "not a url"]) {
|
|
13
|
+
expect(httpsUrlOrNull(url)).toBeNull();
|
|
14
|
+
}
|
|
15
|
+
});
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
describe("citeAs", () => {
|
|
19
|
+
it("builds a markdown link from a label and URL", () => {
|
|
20
|
+
expect(citeAs("Quarterly report", "https://www.google.com/calendar/event?eid=abc")).toBe(
|
|
21
|
+
"[Quarterly report](https://www.google.com/calendar/event?eid=abc)",
|
|
22
|
+
);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it("returns null without a usable URL", () => {
|
|
26
|
+
expect(citeAs("Subject", null)).toBeNull();
|
|
27
|
+
expect(citeAs("Subject", "http://insecure.example")).toBeNull();
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it("escapes brackets and flattens whitespace in the label", () => {
|
|
31
|
+
expect(citeAs("Re: [EXT]\n budget\\plan", "https://x.test/a")).toBe("[Re: \\[EXT\\] budget\\\\plan](https://x.test/a)");
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it("falls back to a default label for an empty subject", () => {
|
|
35
|
+
expect(citeAs(" ", "https://x.test/a", "Outlook message")).toBe("[Outlook message](https://x.test/a)");
|
|
36
|
+
expect(citeAs(null, "https://x.test/a")).toBe("[source](https://x.test/a)");
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it("caps long labels", () => {
|
|
40
|
+
const link = citeAs("x".repeat(200), "https://x.test/a")!;
|
|
41
|
+
const label = link.slice(1, link.indexOf("]("));
|
|
42
|
+
expect(label.length).toBe(80);
|
|
43
|
+
expect(label.endsWith("…")).toBe(true);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it("encodes characters that would end the markdown destination", () => {
|
|
47
|
+
const link = citeAs("Event", "https://x.test/path(1)?q=a b")!;
|
|
48
|
+
expect(link).toBe("[Event](https://x.test/path%281%29?q=a%20b)");
|
|
49
|
+
expect(/^\[[^\]]*\]\([^()\s]+\)$/.test(link)).toBe(true);
|
|
50
|
+
});
|
|
51
|
+
});
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Source-link fields for tool results, so an agent can link a reply back to
|
|
3
|
+
* the email, calendar event, or chat it came from without building URLs.
|
|
4
|
+
*
|
|
5
|
+
* `cite_as` is a ready-made markdown link the system prompt tells agents to
|
|
6
|
+
* paste verbatim. Building it in code keeps the label and URL well-formed:
|
|
7
|
+
* a stray `]` in a subject or `)` in a URL would otherwise break the link.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const MAX_LABEL_CHARS = 80;
|
|
11
|
+
|
|
12
|
+
/** Returns `url` when it is an absolute https URL, otherwise null. */
|
|
13
|
+
export function httpsUrlOrNull(url: string | null | undefined): string | null {
|
|
14
|
+
if (!url) return null;
|
|
15
|
+
try {
|
|
16
|
+
const parsed = new URL(url);
|
|
17
|
+
return parsed.protocol === "https:" ? parsed.href : null;
|
|
18
|
+
} catch {
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function markdownLabel(label: string | null | undefined, fallback: string): string {
|
|
24
|
+
const flat = (label ?? "").replace(/\s+/g, " ").trim();
|
|
25
|
+
const capped = flat.length > MAX_LABEL_CHARS ? `${flat.slice(0, MAX_LABEL_CHARS - 1).trimEnd()}…` : flat;
|
|
26
|
+
return (capped || fallback).replace(/[\\[\]]/g, (c) => `\\${c}`);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function markdownDestination(url: string): string {
|
|
30
|
+
return url.replace(/[()\s<>]/g, (c) => `%${c.charCodeAt(0).toString(16).toUpperCase().padStart(2, "0")}`);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Pre-built markdown link for `cite_as`, or null when there is no usable URL.
|
|
35
|
+
* Only https URLs qualify: the chat renderer drops other schemes.
|
|
36
|
+
*/
|
|
37
|
+
export function citeAs(
|
|
38
|
+
label: string | null | undefined,
|
|
39
|
+
url: string | null | undefined,
|
|
40
|
+
fallbackLabel = "source",
|
|
41
|
+
): string | null {
|
|
42
|
+
const safe = httpsUrlOrNull(url);
|
|
43
|
+
if (!safe) return null;
|
|
44
|
+
return `[${markdownLabel(label, fallbackLabel)}](${markdownDestination(safe)})`;
|
|
45
|
+
}
|
package/package.json
CHANGED
|
File without changes
|
|
File without changes
|