ris-mcp-ts 1.4.2 → 1.5.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/README.md +6 -4
- package/dist/document-cache.d.ts +51 -0
- package/dist/document-cache.d.ts.map +1 -0
- package/dist/document-cache.js +117 -0
- package/dist/document-cache.js.map +1 -0
- package/dist/document-loader.d.ts +45 -0
- package/dist/document-loader.d.ts.map +1 -0
- package/dist/document-loader.js +186 -0
- package/dist/document-loader.js.map +1 -0
- package/dist/formatting.d.ts +55 -0
- package/dist/formatting.d.ts.map +1 -1
- package/dist/formatting.js +164 -16
- package/dist/formatting.js.map +1 -1
- package/dist/generated/trefferliste-html.d.ts.map +1 -1
- package/dist/generated/trefferliste-html.js +1 -1
- package/dist/generated/trefferliste-html.js.map +1 -1
- package/dist/generated/viewer-html.d.ts +2 -0
- package/dist/generated/viewer-html.d.ts.map +1 -0
- package/dist/generated/viewer-html.js +3 -0
- package/dist/generated/viewer-html.js.map +1 -0
- package/dist/helpers.d.ts +8 -1
- package/dist/helpers.d.ts.map +1 -1
- package/dist/helpers.js.map +1 -1
- package/dist/http.d.ts.map +1 -1
- package/dist/http.js +8 -1
- package/dist/http.js.map +1 -1
- package/dist/tools/dokument-abschnitt.d.ts +12 -0
- package/dist/tools/dokument-abschnitt.d.ts.map +1 -0
- package/dist/tools/dokument-abschnitt.js +87 -0
- package/dist/tools/dokument-abschnitt.js.map +1 -0
- package/dist/tools/dokument.d.ts +2 -1
- package/dist/tools/dokument.d.ts.map +1 -1
- package/dist/tools/dokument.js +79 -152
- package/dist/tools/dokument.js.map +1 -1
- package/dist/tools/index.d.ts +8 -2
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +13 -3
- package/dist/tools/index.js.map +1 -1
- package/dist/types.d.ts +60 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +77 -0
- package/dist/types.js.map +1 -1
- package/dist/widgets.d.ts +48 -3
- package/dist/widgets.d.ts.map +1 -1
- package/dist/widgets.js +82 -31
- package/dist/widgets.js.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -162,7 +162,9 @@ npx -y ris-mcp-ts
|
|
|
162
162
|
|
|
163
163
|
## Interactive Result List
|
|
164
164
|
|
|
165
|
-
**One widget, all eleven search tools.** There is a single interactive result list, and every search tool renders into it: federal law, state law, case law, the federal and state law gazettes, government bills, district authority announcements, municipal law, the miscellaneous collections, change history and the ordinance gazettes.
|
|
165
|
+
**One widget, all eleven search tools.** There is a single interactive result list, and every search tool renders into it: federal law, state law, case law, the federal and state law gazettes, government bills, district authority announcements, municipal law, the miscellaneous collections, change history and the ordinance gazettes. The screenshots below are the same widget in two different hosts, not two different features.
|
|
166
|
+
|
|
167
|
+
**`ris_dokument` opens a reader instead.** A single document can run to a quarter of a million characters, far past what one answer may carry, so the viewer loads it section by section as you scroll, offers an outline to jump by where the document has one, and links back into RIS. The full text still reaches the chat either way — the widget is never the only copy.
|
|
166
168
|
|
|
167
169
|
Hosts that support the MCP Apps extension render the hits as an interactive list — page through results without retyping the search, open a document in the browser, or request its full text with one click. The widget follows the host's light or dark theme; the shots below show both.
|
|
168
170
|
|
|
@@ -199,11 +201,11 @@ In a host without MCP Apps support the same prompts return the full text answer
|
|
|
199
201
|
|
|
200
202
|
| Host | What you get |
|
|
201
203
|
|------|--------------|
|
|
202
|
-
| claude.ai |
|
|
203
|
-
| ChatGPT |
|
|
204
|
+
| claude.ai | Result list (pagination, document links) + document viewer (outline navigation, lazy loading) |
|
|
205
|
+
| ChatGPT | Result list (pagination) + document viewer (outline navigation, lazy loading) |
|
|
204
206
|
| Claude Code, and any client without MCP Apps support | The regular text results, unchanged |
|
|
205
207
|
|
|
206
|
-
The chat answer is always complete. The
|
|
208
|
+
The chat answer is always complete. The widgets are an addition to it, never a replacement — a host that does not render them loses nothing.
|
|
207
209
|
|
|
208
210
|
## Tool Reference
|
|
209
211
|
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* In-memory cache for documents the viewer pages through.
|
|
3
|
+
*
|
|
4
|
+
* The chunk tool addresses a document by character offset, so it has to keep
|
|
5
|
+
* reading the *same* string the first response was cut from — a re-fetch can
|
|
6
|
+
* produce a different metadata header and shift every offset. The cache is what
|
|
7
|
+
* makes that string outlive a single tool call.
|
|
8
|
+
*
|
|
9
|
+
* It is created per `registerAllTools()` call, which means per session on HTTP
|
|
10
|
+
* and per process on stdio, and is bounded by a character budget rather than by
|
|
11
|
+
* entry count alone: with `MAX_SESSIONS = 100` any per-session entry limit is
|
|
12
|
+
* multiplied by a hundred, and RIS documents differ in size by two orders of
|
|
13
|
+
* magnitude.
|
|
14
|
+
*/
|
|
15
|
+
import type { OutlineEntry } from './formatting.js';
|
|
16
|
+
/** A document as the viewer consumes it: rendered text plus its jump targets. */
|
|
17
|
+
export interface CachedDocument {
|
|
18
|
+
/** Markdown rendering, exactly as ris_dokument produced it — before truncation. */
|
|
19
|
+
text: string;
|
|
20
|
+
outline: OutlineEntry[];
|
|
21
|
+
/** URL the HTML came from. */
|
|
22
|
+
sourceUrl: string;
|
|
23
|
+
}
|
|
24
|
+
export interface DocumentCache {
|
|
25
|
+
/** Looks up `key` directly, then as an alias. */
|
|
26
|
+
get(key: string): CachedDocument | undefined;
|
|
27
|
+
/** `alias` is the resolved content URL; ignored when equal to `key`. */
|
|
28
|
+
set(key: string, value: CachedDocument, alias?: string): void;
|
|
29
|
+
/** Live entries — for tests and diagnostics. */
|
|
30
|
+
readonly size: number;
|
|
31
|
+
/** Characters currently held — the bound that actually matters. */
|
|
32
|
+
readonly chars: number;
|
|
33
|
+
}
|
|
34
|
+
export interface DocumentCacheOptions {
|
|
35
|
+
maxEntries?: number;
|
|
36
|
+
maxChars?: number;
|
|
37
|
+
ttlMs?: number;
|
|
38
|
+
/** Injectable clock; tests drive TTL expiry without timers. */
|
|
39
|
+
now?: () => number;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Create a bounded, least-recently-used document cache.
|
|
43
|
+
*
|
|
44
|
+
* A document is reachable under two keys: its Dokumentnummer and its resolved
|
|
45
|
+
* content URL. `ris_dokument` and the chunk tool may each be called with either
|
|
46
|
+
* one, and nothing guarantees the viewer holds the same identifier the document
|
|
47
|
+
* was opened with — a single key would miss on exactly that crossing and turn
|
|
48
|
+
* every chunk into a fetch, which looks correct and is merely slow.
|
|
49
|
+
*/
|
|
50
|
+
export declare function createDocumentCache(options?: DocumentCacheOptions): DocumentCache;
|
|
51
|
+
//# sourceMappingURL=document-cache.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"document-cache.d.ts","sourceRoot":"","sources":["../src/document-cache.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAEpD,iFAAiF;AACjF,MAAM,WAAW,cAAc;IAC7B,mFAAmF;IACnF,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,YAAY,EAAE,CAAC;IACxB,8BAA8B;IAC9B,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,aAAa;IAC5B,iDAAiD;IACjD,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,cAAc,GAAG,SAAS,CAAC;IAC7C,wEAAwE;IACxE,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,cAAc,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9D,gDAAgD;IAChD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,mEAAmE;IACnE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,oBAAoB;IACnC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,+DAA+D;IAC/D,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;CACpB;AAWD;;;;;;;;GAQG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,GAAE,oBAAyB,GAAG,aAAa,CAuGrF"}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* In-memory cache for documents the viewer pages through.
|
|
3
|
+
*
|
|
4
|
+
* The chunk tool addresses a document by character offset, so it has to keep
|
|
5
|
+
* reading the *same* string the first response was cut from — a re-fetch can
|
|
6
|
+
* produce a different metadata header and shift every offset. The cache is what
|
|
7
|
+
* makes that string outlive a single tool call.
|
|
8
|
+
*
|
|
9
|
+
* It is created per `registerAllTools()` call, which means per session on HTTP
|
|
10
|
+
* and per process on stdio, and is bounded by a character budget rather than by
|
|
11
|
+
* entry count alone: with `MAX_SESSIONS = 100` any per-session entry limit is
|
|
12
|
+
* multiplied by a hundred, and RIS documents differ in size by two orders of
|
|
13
|
+
* magnitude.
|
|
14
|
+
*/
|
|
15
|
+
const DEFAULT_MAX_ENTRIES = 10;
|
|
16
|
+
const DEFAULT_MAX_CHARS = 1_000_000;
|
|
17
|
+
const DEFAULT_TTL_MS = 600_000;
|
|
18
|
+
/**
|
|
19
|
+
* Create a bounded, least-recently-used document cache.
|
|
20
|
+
*
|
|
21
|
+
* A document is reachable under two keys: its Dokumentnummer and its resolved
|
|
22
|
+
* content URL. `ris_dokument` and the chunk tool may each be called with either
|
|
23
|
+
* one, and nothing guarantees the viewer holds the same identifier the document
|
|
24
|
+
* was opened with — a single key would miss on exactly that crossing and turn
|
|
25
|
+
* every chunk into a fetch, which looks correct and is merely slow.
|
|
26
|
+
*/
|
|
27
|
+
export function createDocumentCache(options = {}) {
|
|
28
|
+
const maxEntries = options.maxEntries ?? DEFAULT_MAX_ENTRIES;
|
|
29
|
+
const maxChars = options.maxChars ?? DEFAULT_MAX_CHARS;
|
|
30
|
+
const ttlMs = options.ttlMs ?? DEFAULT_TTL_MS;
|
|
31
|
+
const now = options.now ?? Date.now;
|
|
32
|
+
/** Insertion order doubles as LRU order: oldest first. */
|
|
33
|
+
const entries = new Map();
|
|
34
|
+
/** alias → primary key. Holds no copy of the text, so it costs no budget. */
|
|
35
|
+
const aliases = new Map();
|
|
36
|
+
let chars = 0;
|
|
37
|
+
function drop(key) {
|
|
38
|
+
const entry = entries.get(key);
|
|
39
|
+
if (!entry) {
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
entries.delete(key);
|
|
43
|
+
chars -= entry.value.text.length;
|
|
44
|
+
for (const [alias, primary] of aliases) {
|
|
45
|
+
if (primary === key) {
|
|
46
|
+
aliases.delete(alias);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return {
|
|
51
|
+
get(key) {
|
|
52
|
+
const primary = entries.has(key) ? key : aliases.get(key);
|
|
53
|
+
if (primary === undefined) {
|
|
54
|
+
return undefined;
|
|
55
|
+
}
|
|
56
|
+
const entry = entries.get(primary);
|
|
57
|
+
if (!entry) {
|
|
58
|
+
// A dangling alias — its entry was evicted. Nothing else refers to it.
|
|
59
|
+
aliases.delete(key);
|
|
60
|
+
return undefined;
|
|
61
|
+
}
|
|
62
|
+
// TTL is checked on read rather than on a timer: a timer would keep the
|
|
63
|
+
// process alive and buy nothing, since an unread entry costs only memory
|
|
64
|
+
// the eviction below reclaims anyway.
|
|
65
|
+
if (now() - entry.storedAt > ttlMs) {
|
|
66
|
+
drop(primary);
|
|
67
|
+
return undefined;
|
|
68
|
+
}
|
|
69
|
+
// Re-insert to move the entry to the young end of the map.
|
|
70
|
+
entries.delete(primary);
|
|
71
|
+
entries.set(primary, entry);
|
|
72
|
+
return entry.value;
|
|
73
|
+
},
|
|
74
|
+
set(key, value, alias) {
|
|
75
|
+
const hasAlias = alias !== undefined && alias !== key;
|
|
76
|
+
// Every previous copy of this document goes first, under either name —
|
|
77
|
+
// before the oversize check below, which stores nothing in its place.
|
|
78
|
+
// Dropping afterwards would leave a document that has grown past the
|
|
79
|
+
// budget being served from its old, shorter text while ris_dokument
|
|
80
|
+
// renders the new one, and the viewer would page against stale offsets.
|
|
81
|
+
// The alias may also have been a primary key of its own (the document was
|
|
82
|
+
// opened by URL and now arrives by Dokumentnummer), which is the same
|
|
83
|
+
// document under a second copy.
|
|
84
|
+
drop(key);
|
|
85
|
+
aliases.delete(key);
|
|
86
|
+
if (hasAlias) {
|
|
87
|
+
drop(alias);
|
|
88
|
+
aliases.delete(alias);
|
|
89
|
+
}
|
|
90
|
+
// Caching a document larger than the whole budget would evict everything
|
|
91
|
+
// else and still overshoot, so it is not cached at all. The chunk path
|
|
92
|
+
// stays correct, it just re-fetches per call.
|
|
93
|
+
if (value.text.length > maxChars) {
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
entries.set(key, { value, storedAt: now() });
|
|
97
|
+
chars += value.text.length;
|
|
98
|
+
if (hasAlias) {
|
|
99
|
+
aliases.set(alias, key);
|
|
100
|
+
}
|
|
101
|
+
while (entries.size > maxEntries || chars > maxChars) {
|
|
102
|
+
const oldest = entries.keys().next().value;
|
|
103
|
+
if (oldest === undefined) {
|
|
104
|
+
break;
|
|
105
|
+
}
|
|
106
|
+
drop(oldest);
|
|
107
|
+
}
|
|
108
|
+
},
|
|
109
|
+
get size() {
|
|
110
|
+
return entries.size;
|
|
111
|
+
},
|
|
112
|
+
get chars() {
|
|
113
|
+
return chars;
|
|
114
|
+
},
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
//# sourceMappingURL=document-cache.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"document-cache.js","sourceRoot":"","sources":["../src/document-cache.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared load path for a single RIS document.
|
|
3
|
+
*
|
|
4
|
+
* `ris_dokument` and `ris_dokument_abschnitt` both need the full text of a
|
|
5
|
+
* document, and they need the *same* text: the chunk tool hands out character
|
|
6
|
+
* offsets into it, and the two resolution strategies below produce different
|
|
7
|
+
* metadata headers, so a document loaded one way is several thousand characters
|
|
8
|
+
* offset against the same document loaded the other way. Sharing the path is
|
|
9
|
+
* what keeps a chunk pointing at what the reader was shown.
|
|
10
|
+
*
|
|
11
|
+
* Everything here — the validation, both SSRF checks, the direct/search
|
|
12
|
+
* strategy and the German error prose — was lifted out of `tools/dokument.ts`
|
|
13
|
+
* unchanged; the strings are part of that tool's response contract.
|
|
14
|
+
*/
|
|
15
|
+
import { type DocumentMetadata } from './formatting.js';
|
|
16
|
+
export interface LoadedDocument {
|
|
17
|
+
/** Markdown (or JSON) rendering, before truncation. */
|
|
18
|
+
text: string;
|
|
19
|
+
/** RIS source HTML — needed for the outline; callers may drop it. */
|
|
20
|
+
html: string;
|
|
21
|
+
/** URL the HTML came from; feeds `source_url` and the document cache key. */
|
|
22
|
+
contentUrl: string;
|
|
23
|
+
metadata: DocumentMetadata;
|
|
24
|
+
}
|
|
25
|
+
export type LoadDocumentResult = {
|
|
26
|
+
success: true;
|
|
27
|
+
document: LoadedDocument;
|
|
28
|
+
} | {
|
|
29
|
+
success: false;
|
|
30
|
+
error: string;
|
|
31
|
+
};
|
|
32
|
+
export interface LoadDocumentArgs {
|
|
33
|
+
dokumentnummer?: string;
|
|
34
|
+
url?: string;
|
|
35
|
+
responseFormat: 'markdown' | 'json';
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Resolve, fetch and format a document.
|
|
39
|
+
*
|
|
40
|
+
* Returns a German error string for every condition the user can fix. Throws on
|
|
41
|
+
* cancellation and on unexpected upstream failures — callers keep the abort and
|
|
42
|
+
* `formatErrorResponse` handling they already have.
|
|
43
|
+
*/
|
|
44
|
+
export declare function loadDocument(args: LoadDocumentArgs, signal: AbortSignal): Promise<LoadDocumentResult>;
|
|
45
|
+
//# sourceMappingURL=document-loader.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"document-loader.d.ts","sourceRoot":"","sources":["../src/document-loader.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAaH,OAAO,EAAkB,KAAK,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAIxE,MAAM,WAAW,cAAc;IAC7B,uDAAuD;IACvD,IAAI,EAAE,MAAM,CAAC;IACb,qEAAqE;IACrE,IAAI,EAAE,MAAM,CAAC;IACb,6EAA6E;IAC7E,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,gBAAgB,CAAC;CAC5B;AAED,MAAM,MAAM,kBAAkB,GAC1B;IAAE,OAAO,EAAE,IAAI,CAAC;IAAC,QAAQ,EAAE,cAAc,CAAA;CAAE,GAC3C;IAAE,OAAO,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAEtC,MAAM,WAAW,gBAAgB;IAC/B,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,cAAc,EAAE,UAAU,GAAG,MAAM,CAAC;CACrC;AAED;;;;;;GAMG;AACH,wBAAsB,YAAY,CAChC,IAAI,EAAE,gBAAgB,EACtB,MAAM,EAAE,WAAW,GAClB,OAAO,CAAC,kBAAkB,CAAC,CAkL7B"}
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared load path for a single RIS document.
|
|
3
|
+
*
|
|
4
|
+
* `ris_dokument` and `ris_dokument_abschnitt` both need the full text of a
|
|
5
|
+
* document, and they need the *same* text: the chunk tool hands out character
|
|
6
|
+
* offsets into it, and the two resolution strategies below produce different
|
|
7
|
+
* metadata headers, so a document loaded one way is several thousand characters
|
|
8
|
+
* offset against the same document loaded the other way. Sharing the path is
|
|
9
|
+
* what keeps a chunk pointing at what the reader was shown.
|
|
10
|
+
*
|
|
11
|
+
* Everything here — the validation, both SSRF checks, the direct/search
|
|
12
|
+
* strategy and the German error prose — was lifted out of `tools/dokument.ts`
|
|
13
|
+
* unchanged; the strings are part of that tool's response contract.
|
|
14
|
+
*/
|
|
15
|
+
import { getDocumentByNumber, getDocumentContent, getDocumentRoute, isAllowedUrl, searchBezirke, searchBundesrecht, searchJudikatur, searchLandesrecht, searchSonstige, } from './client.js';
|
|
16
|
+
import { formatDocument } from './formatting.js';
|
|
17
|
+
import { findDocumentByDokumentnummer } from './parser.js';
|
|
18
|
+
/**
|
|
19
|
+
* Resolve, fetch and format a document.
|
|
20
|
+
*
|
|
21
|
+
* Returns a German error string for every condition the user can fix. Throws on
|
|
22
|
+
* cancellation and on unexpected upstream failures — callers keep the abort and
|
|
23
|
+
* `formatErrorResponse` handling they already have.
|
|
24
|
+
*/
|
|
25
|
+
export async function loadDocument(args, signal) {
|
|
26
|
+
const { dokumentnummer, url: inputUrl, responseFormat } = args;
|
|
27
|
+
if (!dokumentnummer && !inputUrl) {
|
|
28
|
+
return {
|
|
29
|
+
success: false,
|
|
30
|
+
error: '**Fehler:** Bitte gib entweder eine `dokumentnummer` oder eine `url` an.\n\n' +
|
|
31
|
+
'Die Dokumentnummer findest du in den Suchergebnissen von `ris_bundesrecht`, ' +
|
|
32
|
+
'`ris_landesrecht` oder `ris_judikatur`.',
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
// SSRF protection: validate user-supplied URLs against domain allowlist
|
|
36
|
+
if (inputUrl && !isAllowedUrl(inputUrl)) {
|
|
37
|
+
return {
|
|
38
|
+
success: false,
|
|
39
|
+
error: '**Fehler:** Die angegebene URL ist nicht erlaubt.\n\n' +
|
|
40
|
+
'Nur HTTPS-URLs zu offiziellen RIS-Domains sind zulaessig ' +
|
|
41
|
+
'(data.bka.gv.at, www.ris.bka.gv.at, ris.bka.gv.at).',
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
let contentUrl = inputUrl;
|
|
45
|
+
let htmlContent;
|
|
46
|
+
let metadata;
|
|
47
|
+
if (dokumentnummer && !inputUrl) {
|
|
48
|
+
// Strategy: Try direct URL construction first, fallback to search API
|
|
49
|
+
const directResult = await getDocumentByNumber(dokumentnummer, undefined, signal);
|
|
50
|
+
if (directResult.success) {
|
|
51
|
+
// Direct fetch succeeded - use minimal metadata
|
|
52
|
+
htmlContent = directResult.html;
|
|
53
|
+
contentUrl = directResult.url;
|
|
54
|
+
metadata = {
|
|
55
|
+
dokumentnummer,
|
|
56
|
+
applikation: 'Unbekannt',
|
|
57
|
+
titel: dokumentnummer,
|
|
58
|
+
kurztitel: null,
|
|
59
|
+
citation: {},
|
|
60
|
+
dokument_url: directResult.url,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
// Direct fetch failed - fallback to search API. Routing (endpoint +
|
|
65
|
+
// Applikation) comes from the shared registry in client.ts so it stays
|
|
66
|
+
// consistent with the direct-URL construction. Unknown prefixes default
|
|
67
|
+
// to a Justiz search.
|
|
68
|
+
const route = getDocumentRoute(dokumentnummer);
|
|
69
|
+
const searchParams = {
|
|
70
|
+
Applikation: route?.applikation ?? 'Justiz',
|
|
71
|
+
Dokumentnummer: dokumentnummer,
|
|
72
|
+
DokumenteProSeite: 'Ten',
|
|
73
|
+
};
|
|
74
|
+
let apiResponse;
|
|
75
|
+
switch (route?.endpoint) {
|
|
76
|
+
case 'Bundesrecht':
|
|
77
|
+
apiResponse = await searchBundesrecht(searchParams, undefined, signal);
|
|
78
|
+
break;
|
|
79
|
+
case 'Landesrecht':
|
|
80
|
+
apiResponse = await searchLandesrecht(searchParams, undefined, signal);
|
|
81
|
+
break;
|
|
82
|
+
case 'Sonstige':
|
|
83
|
+
apiResponse = await searchSonstige(searchParams, undefined, signal);
|
|
84
|
+
break;
|
|
85
|
+
case 'Bezirke':
|
|
86
|
+
apiResponse = await searchBezirke(searchParams, undefined, signal);
|
|
87
|
+
break;
|
|
88
|
+
case 'Judikatur':
|
|
89
|
+
default:
|
|
90
|
+
apiResponse = await searchJudikatur(searchParams, undefined, signal);
|
|
91
|
+
break;
|
|
92
|
+
}
|
|
93
|
+
// Find the document with matching dokumentnummer (don't blindly take first result)
|
|
94
|
+
const findResult = findDocumentByDokumentnummer(apiResponse.documents, dokumentnummer);
|
|
95
|
+
if (!findResult.success) {
|
|
96
|
+
// Both direct fetch and search failed - provide helpful error
|
|
97
|
+
const directError = directResult.error;
|
|
98
|
+
if (findResult.error === 'no_documents') {
|
|
99
|
+
return {
|
|
100
|
+
success: false,
|
|
101
|
+
error: `**Fehler:** Kein Dokument mit der Nummer \`${dokumentnummer}\` gefunden.\n\n` +
|
|
102
|
+
`Direkter Abruf: ${directError}\n` +
|
|
103
|
+
`Suche: Keine Ergebnisse.\n\n` +
|
|
104
|
+
'Bitte pruefe die Dokumentnummer oder verwende eine Suche, ' +
|
|
105
|
+
'um das gewuenschte Dokument zu finden.',
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
else {
|
|
109
|
+
return {
|
|
110
|
+
success: false,
|
|
111
|
+
error: `**Fehler:** Dokument \`${dokumentnummer}\` nicht gefunden.\n\n` +
|
|
112
|
+
`Direkter Abruf: ${directError}\n` +
|
|
113
|
+
`Suche: ${findResult.totalResults} Ergebnisse, aber keines mit dieser Dokumentnummer.\n\n` +
|
|
114
|
+
`Bitte verwende eine alternative Suche oder die direkte URL.`,
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
const doc = findResult.document;
|
|
119
|
+
contentUrl = doc.content_urls.html ?? undefined;
|
|
120
|
+
if (!contentUrl) {
|
|
121
|
+
return {
|
|
122
|
+
success: false,
|
|
123
|
+
error: `**Fehler:** Keine Inhalts-URL fuer Dokument \`${dokumentnummer}\` verfuegbar.\n\n` +
|
|
124
|
+
'Das Dokument hat moeglicherweise keinen abrufbaren Volltext.',
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
// SSRF protection: this URL comes straight from the search API response
|
|
128
|
+
// and would otherwise be fetched unchecked. Validate it against the same
|
|
129
|
+
// domain allowlist used for user-supplied URLs.
|
|
130
|
+
if (!isAllowedUrl(contentUrl)) {
|
|
131
|
+
return {
|
|
132
|
+
success: false,
|
|
133
|
+
error: '**Fehler:** Die Inhalts-URL des Dokuments ist nicht erlaubt.\n\n' +
|
|
134
|
+
'Nur HTTPS-URLs zu offiziellen RIS-Domains sind zulaessig ' +
|
|
135
|
+
'(data.bka.gv.at, www.ris.bka.gv.at, ris.bka.gv.at).',
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
// Build metadata from search result
|
|
139
|
+
metadata = {
|
|
140
|
+
dokumentnummer: doc.dokumentnummer,
|
|
141
|
+
applikation: doc.applikation,
|
|
142
|
+
titel: doc.titel,
|
|
143
|
+
kurztitel: doc.kurztitel,
|
|
144
|
+
citation: {
|
|
145
|
+
kurztitel: doc.citation.kurztitel,
|
|
146
|
+
langtitel: doc.citation.langtitel,
|
|
147
|
+
kundmachungsorgan: doc.citation.kundmachungsorgan,
|
|
148
|
+
paragraph: doc.citation.paragraph,
|
|
149
|
+
eli: doc.citation.eli,
|
|
150
|
+
inkrafttreten: doc.citation.inkrafttreten,
|
|
151
|
+
ausserkrafttreten: doc.citation.ausserkrafttreten,
|
|
152
|
+
},
|
|
153
|
+
dokument_url: doc.dokument_url,
|
|
154
|
+
gesamte_rechtsvorschrift_url: doc.gesamte_rechtsvorschrift_url,
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
else {
|
|
159
|
+
// Only URL provided - minimal metadata
|
|
160
|
+
metadata = {
|
|
161
|
+
dokumentnummer: dokumentnummer ?? 'Unbekannt',
|
|
162
|
+
applikation: 'Unbekannt',
|
|
163
|
+
titel: inputUrl ?? '',
|
|
164
|
+
kurztitel: null,
|
|
165
|
+
citation: {},
|
|
166
|
+
dokument_url: inputUrl,
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
if (!contentUrl) {
|
|
170
|
+
return { success: false, error: '**Fehler:** Keine gueltige URL zum Abrufen des Dokuments.' };
|
|
171
|
+
}
|
|
172
|
+
// Fetch document content if not already fetched via direct URL
|
|
173
|
+
if (!htmlContent) {
|
|
174
|
+
htmlContent = await getDocumentContent(contentUrl, undefined, signal);
|
|
175
|
+
}
|
|
176
|
+
return {
|
|
177
|
+
success: true,
|
|
178
|
+
document: {
|
|
179
|
+
text: formatDocument(htmlContent, metadata, responseFormat),
|
|
180
|
+
html: htmlContent,
|
|
181
|
+
contentUrl,
|
|
182
|
+
metadata,
|
|
183
|
+
},
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
//# sourceMappingURL=document-loader.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"document-loader.js","sourceRoot":"","sources":["../src/document-loader.ts"],"names":[],"mappings":"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"}
|
package/dist/formatting.d.ts
CHANGED
|
@@ -6,6 +6,8 @@
|
|
|
6
6
|
* formatting, and document content preparation.
|
|
7
7
|
*/
|
|
8
8
|
import type { Document, SearchResult } from './types.js';
|
|
9
|
+
/** Hard limit for a single text response, in characters. */
|
|
10
|
+
export declare const CHARACTER_LIMIT = 25000;
|
|
9
11
|
/**
|
|
10
12
|
* Convert API date format to readable German format.
|
|
11
13
|
*/
|
|
@@ -62,6 +64,59 @@ export interface DocumentMetadata {
|
|
|
62
64
|
* Format a full document for LLM context.
|
|
63
65
|
*/
|
|
64
66
|
export declare function formatDocument(content: string, metadata: DocumentMetadata, format?: 'markdown' | 'json'): string;
|
|
67
|
+
/** One jump target in a document, as offered by the viewer's outline. */
|
|
68
|
+
export interface OutlineEntry {
|
|
69
|
+
/**
|
|
70
|
+
* Heading level 1–6, taken from the RIS source markup. Level 1 is the RIS
|
|
71
|
+
* record layer (field labels plus, in gazettes, the masthead); levels 2 and
|
|
72
|
+
* below are structure inside the document text.
|
|
73
|
+
*/
|
|
74
|
+
level: number;
|
|
75
|
+
/** Heading text, whitespace-normalised exactly as the document text is. */
|
|
76
|
+
label: string;
|
|
77
|
+
/** Character offset of the heading's line in the document text. */
|
|
78
|
+
offset: number;
|
|
79
|
+
/**
|
|
80
|
+
* Characters up to the next heading, or to the end of the text for the last
|
|
81
|
+
* entry. The only reliable way to tell a one-line metadata field from a real
|
|
82
|
+
* section: RIS gives both the same element and the same class.
|
|
83
|
+
*/
|
|
84
|
+
span: number;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Extract the jump targets of a document.
|
|
88
|
+
*
|
|
89
|
+
* `html` is the RIS source, `text` the markdown rendering it produced. The
|
|
90
|
+
* headings are read from the markup — the rendered text carries no structure
|
|
91
|
+
* beyond the three headings formatDocument() writes itself — and then located in
|
|
92
|
+
* `text` by their own line.
|
|
93
|
+
*
|
|
94
|
+
* A document without headings yields `[]`. That is a valid outline, not an
|
|
95
|
+
* error: the viewer stays navigable through plain offset paging.
|
|
96
|
+
*/
|
|
97
|
+
export declare function extractOutline(html: string, text: string): OutlineEntry[];
|
|
98
|
+
/** One slice of a document text, addressed by character offset. */
|
|
99
|
+
export interface ResponseChunk {
|
|
100
|
+
/** The slice starting at the requested offset, at most `limit` characters. */
|
|
101
|
+
text: string;
|
|
102
|
+
/** Length of the complete text this chunk was cut from. */
|
|
103
|
+
total_length: number;
|
|
104
|
+
/** Offset for the following chunk, or null when this chunk ends the text. */
|
|
105
|
+
next_offset: number | null;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Cut one chunk out of `text`, starting at `offset`.
|
|
109
|
+
*
|
|
110
|
+
* Chunk boundaries follow the same paragraph-then-sentence rule as
|
|
111
|
+
* {@link truncateResponse}; unlike that function nothing is appended and nothing
|
|
112
|
+
* is dropped — consecutive chunks concatenate back to `text`.
|
|
113
|
+
*
|
|
114
|
+
* Offsets are character offsets into the complete text, i.e. the unit of
|
|
115
|
+
* `String.length` and `String.slice`. They stay valid as long as the same text
|
|
116
|
+
* does, which is why every chunk carries `total_length`: a caller whose text was
|
|
117
|
+
* re-fetched in between sees the length change and restarts at offset 0.
|
|
118
|
+
*/
|
|
119
|
+
export declare function chunkResponse(text: string, offset?: number, limit?: number): ResponseChunk;
|
|
65
120
|
/**
|
|
66
121
|
* Truncate response if too long.
|
|
67
122
|
*/
|
package/dist/formatting.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"formatting.d.ts","sourceRoot":"","sources":["../src/formatting.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH,OAAO,KAAK,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"formatting.d.ts","sourceRoot":"","sources":["../src/formatting.ts"],"names":[],"mappings":"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"}
|