osmassist 0.0.0-stage → 1.0.0-alpha.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.
- package/LICENSE +21 -0
- package/README.md +145 -2
- package/dist/answerFile.d.ts +185 -0
- package/dist/answerFile.js +215 -0
- package/dist/answerFile.js.map +1 -0
- package/dist/check.d.ts +22 -0
- package/dist/check.js +161 -0
- package/dist/check.js.map +1 -0
- package/dist/claude.d.ts +19 -0
- package/dist/claude.js +134 -0
- package/dist/claude.js.map +1 -0
- package/dist/html.d.ts +8 -0
- package/dist/html.js +108 -0
- package/dist/html.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +10 -0
- package/dist/index.js.map +1 -0
- package/dist/jobFile.d.ts +20 -0
- package/dist/jobFile.js +168 -0
- package/dist/jobFile.js.map +1 -0
- package/dist/models.d.ts +8 -0
- package/dist/models.js +25 -0
- package/dist/models.js.map +1 -0
- package/dist/prompt.d.ts +58 -0
- package/dist/prompt.js +93 -0
- package/dist/prompt.js.map +1 -0
- package/dist/text.d.ts +13 -0
- package/dist/text.js +47 -0
- package/dist/text.js.map +1 -0
- package/dist/types.d.ts +143 -0
- package/dist/types.js +35 -0
- package/dist/types.js.map +1 -0
- package/package.json +62 -4
- package/schema/answer-file.schema.json +205 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Andreas Neumann
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,146 @@
|
|
|
1
|
-
#
|
|
1
|
+
# osmAssist
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Suggests [OpenStreetMap](https://www.openstreetmap.org) tag changes from a
|
|
4
|
+
feature's own website with the help of an AI model: opening hours, phone and
|
|
5
|
+
fax numbers, e-mail addresses, a new website address and linked profiles on
|
|
6
|
+
social networks, plus signals that the business has closed, moved, or that
|
|
7
|
+
the domain is parked or belongs to someone else.
|
|
8
|
+
|
|
9
|
+
The library only makes suggestions. Every suggestion comes with a verbatim
|
|
10
|
+
quote and the page it is from, and must be checked and approved by a human
|
|
11
|
+
before anything is changed in OpenStreetMap. It is used by
|
|
12
|
+
[osmCorrection](https://github.com/cyper85/osmCorrection).
|
|
13
|
+
|
|
14
|
+
## Principles
|
|
15
|
+
|
|
16
|
+
- **One feature at a time, on request.** No bulk edits.
|
|
17
|
+
- **The user's own API key.** The caller pays for the tokens.
|
|
18
|
+
- **Only the feature's website** (the domain from the OSM tag) is fetched.
|
|
19
|
+
Closed or paid networks (Facebook, Instagram, Google …) are never fetched;
|
|
20
|
+
links to profiles are taken from the website only.
|
|
21
|
+
- **No knowledge of the model.** A suggestion is rejected unless its quote is
|
|
22
|
+
found on a fetched page of the site; links and e-mail addresses must
|
|
23
|
+
appear on the page as well.
|
|
24
|
+
|
|
25
|
+
## Installation
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
npm install osmassist
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
ES module with TypeScript types, for Node.js ≥ 22 and browsers (via a
|
|
32
|
+
bundler). The Claude provider is a separate entry point
|
|
33
|
+
(`osmassist/claude`), so a web app loads the Anthropic SDK only where it is
|
|
34
|
+
used. The JSON Schema of the answer files is part of the package
|
|
35
|
+
(`osmassist/schema/answer-file.schema.json`).
|
|
36
|
+
|
|
37
|
+
## Usage
|
|
38
|
+
|
|
39
|
+
```ts
|
|
40
|
+
import { aiAssistedTag } from 'osmassist';
|
|
41
|
+
// separate entry point, so a web app can load the SDK only when it is needed
|
|
42
|
+
import { createClaudeProvider } from 'osmassist/claude';
|
|
43
|
+
|
|
44
|
+
const provider = createClaudeProvider({ apiKey: userKey }); // Claude Sonnet 5.5 by default
|
|
45
|
+
|
|
46
|
+
const result = await provider.analyze(
|
|
47
|
+
{
|
|
48
|
+
element: { type: 'node', id: 123, tags: { shop: 'bakery', website: 'https://example.org/' } },
|
|
49
|
+
url: 'https://example.org/',
|
|
50
|
+
today: '2026-11-01',
|
|
51
|
+
country: 'DE',
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
// format check of the caller, e.g. with osmValidation; returns the preferred form
|
|
55
|
+
validate: (key, value) => ({ valid: true, value }),
|
|
56
|
+
},
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
result.proposals; // [{ key: 'opening_hours', value: 'Mo-Fr 06:00-18:00', oldValue?, quote, sourceUrl }]
|
|
60
|
+
result.signals; // [{ kind: 'closed' | 'moved' | 'parked' | 'new_owner', quote, sourceUrl }]
|
|
61
|
+
result.rejected; // suggestions that failed a check, with reason
|
|
62
|
+
result.pages; // fetched pages, with an error code where fetching failed
|
|
63
|
+
result.usage; // tokens and estimated cost in USD
|
|
64
|
+
aiAssistedTag([result.model]); // 'Anthropic Claude Sonnet 5.5', for the changeset
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
`analyze` is `read` and `assess` in one step. `read` costs tokens and does
|
|
68
|
+
not depend on the current tags: the model reports everything the site says.
|
|
69
|
+
`assess(reading, request, validate)` compares it with the tags of one
|
|
70
|
+
element and costs nothing, so a reading can be reused for other elements
|
|
71
|
+
with the same website (and the same address, if the site lists several
|
|
72
|
+
locations):
|
|
73
|
+
|
|
74
|
+
```ts
|
|
75
|
+
import { assess } from 'osmassist';
|
|
76
|
+
|
|
77
|
+
const reading = await provider.read(request);
|
|
78
|
+
assess(reading, request, validate);
|
|
79
|
+
assess(reading, { ...request, element: otherElement }, validate);
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Keys follow the form the feature already uses (`phone` or `contact:phone`);
|
|
83
|
+
new contact tags get `contact:*`.
|
|
84
|
+
|
|
85
|
+
Errors are thrown as `AssistError` with a `code` (`auth`, `rate_limit`,
|
|
86
|
+
`refused`, `incomplete`, `invalid_response`, `api`, `aborted`). Messages are
|
|
87
|
+
in English; callers translate them by code.
|
|
88
|
+
|
|
89
|
+
## Analysis by an AI agent (e.g. Claude Code)
|
|
90
|
+
|
|
91
|
+
To use an AI plan instead of API credits, the analysis can run in an AI
|
|
92
|
+
agent on the user's computer, e.g. Claude Code with a Claude plan, Kiro or
|
|
93
|
+
Codex CLI: `jobFile` writes the instructions with the element IDs
|
|
94
|
+
(`auftrag.md`), the agent saves the pages (headless browser or `curl`) and
|
|
95
|
+
writes one answer file per job, and the app reads them back:
|
|
96
|
+
|
|
97
|
+
```ts
|
|
98
|
+
import { assess, parseAnswerFile, readingFromAnswer } from 'osmassist';
|
|
99
|
+
|
|
100
|
+
const answer = parseAnswerFile(json); // antworten/a1.json
|
|
101
|
+
answer.ai; // { vendor, model, tool }, as the agent states it
|
|
102
|
+
const reading = readingFromAnswer(answer, pages, vendor); // pages: path → saved HTML; vendor: confirmed by the user
|
|
103
|
+
assess(reading, { element, url: answer.url, today }, validate);
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
The format of the answer files is described in
|
|
107
|
+
[docs/answer-file.md](docs/answer-file.md), with the JSON Schema in
|
|
108
|
+
[schema/answer-file.schema.json](schema/answer-file.schema.json) (also
|
|
109
|
+
exported as `ANSWER_FILE_SCHEMA` and as the package file
|
|
110
|
+
`osmassist/schema/answer-file.schema.json`).
|
|
111
|
+
|
|
112
|
+
`htmlToText` turns the saved HTML into text without a DOM; links keep their
|
|
113
|
+
address, so profile links and e-mail addresses can be checked. `decodeHtml`
|
|
114
|
+
reads saved files in UTF-8, UTF-16 or the charset the page declares.
|
|
115
|
+
|
|
116
|
+
## Claude
|
|
117
|
+
|
|
118
|
+
- Web fetch tool (`web_fetch_20250910`) restricted to the site's domain, at
|
|
119
|
+
most 4 pages of at most 20,000 tokens each. Anthropic fetches the pages, so
|
|
120
|
+
the library also works in the browser without a proxy.
|
|
121
|
+
- Structured output with a fixed JSON schema (`ANSWER_SCHEMA`).
|
|
122
|
+
- Server-side fallback on refusals (`fallbacks: "default"`); the model that
|
|
123
|
+
actually answered is reported in `result.model`.
|
|
124
|
+
- Calls from the browser are enabled (`dangerouslyAllowBrowser`): the key is
|
|
125
|
+
the user's own and only goes to `api.anthropic.com`. Recommend a key with a
|
|
126
|
+
short expiry that is used for nothing else.
|
|
127
|
+
|
|
128
|
+
## Development
|
|
129
|
+
|
|
130
|
+
```bash
|
|
131
|
+
npm install
|
|
132
|
+
npm test # vitest, with recorded API responses (no real API calls)
|
|
133
|
+
npm run build # dist/
|
|
134
|
+
npm run schema # schema/answer-file.schema.json from the code
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
Releases: a GitHub release with a tag like `v1.0.0-alpha.1` (matching `version` in
|
|
138
|
+
`package.json`) publishes the package to npm (`.github/workflows/publish.yaml`,
|
|
139
|
+
npm trusted publishing, no token). Versions with a suffix such as
|
|
140
|
+
`1.0.0-alpha.1` get the npm tag `next`, others `latest`; as long as `latest`
|
|
141
|
+
is only npm's placeholder from reserving the name (`0.0.0-stage`), a
|
|
142
|
+
pre-release becomes `latest` as well.
|
|
143
|
+
|
|
144
|
+
## License
|
|
145
|
+
|
|
146
|
+
MIT
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import { type RawAnswer, type SiteReading } from './types.js';
|
|
2
|
+
export declare const ANSWER_FORMAT = "osmcorrection-assist-answer";
|
|
3
|
+
/** Where the schema file is published (schema/answer-file.schema.json in this repository). */
|
|
4
|
+
export declare const ANSWER_FILE_SCHEMA_ID = "https://raw.githubusercontent.com/cyper85/osmAssist/master/schema/answer-file.schema.json";
|
|
5
|
+
export interface ElementId {
|
|
6
|
+
type: 'node' | 'way' | 'relation';
|
|
7
|
+
id: number;
|
|
8
|
+
}
|
|
9
|
+
export interface AnswerPage {
|
|
10
|
+
url: string;
|
|
11
|
+
/** the saved HTML, relative to the working folder */
|
|
12
|
+
file?: string;
|
|
13
|
+
error?: string;
|
|
14
|
+
}
|
|
15
|
+
/** The AI that wrote the answer, as it states itself. */
|
|
16
|
+
export interface AnswerAi {
|
|
17
|
+
/** maker of the model, e.g. "Anthropic", "OpenAI", "Google" (not the maker of the tool) */
|
|
18
|
+
vendor: string;
|
|
19
|
+
/** e.g. "claude-opus-5-5" */
|
|
20
|
+
model: string;
|
|
21
|
+
/** the agent it ran in, e.g. "Claude Code", "Kiro", "Codex CLI" */
|
|
22
|
+
tool: string;
|
|
23
|
+
}
|
|
24
|
+
export interface AnswerFile extends RawAnswer {
|
|
25
|
+
format: typeof ANSWER_FORMAT;
|
|
26
|
+
version: 1;
|
|
27
|
+
/** required by the schema; older answers (first run, 2026-11-02) do not have it */
|
|
28
|
+
ai?: AnswerAi;
|
|
29
|
+
job: string;
|
|
30
|
+
elements: ElementId[];
|
|
31
|
+
url: string;
|
|
32
|
+
fetched_with?: string;
|
|
33
|
+
pages: AnswerPage[];
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* JSON Schema of an answer file, given to the model in the job file. The
|
|
37
|
+
* entries of `proposals` and `signals` are the ones of the API (ANSWER_SCHEMA).
|
|
38
|
+
*/
|
|
39
|
+
export declare const ANSWER_FILE_SCHEMA: {
|
|
40
|
+
readonly $schema: 'https://json-schema.org/draft/2020-12/schema';
|
|
41
|
+
readonly $id: "https://raw.githubusercontent.com/cyper85/osmAssist/master/schema/answer-file.schema.json";
|
|
42
|
+
readonly title: 'osmAssist answer file';
|
|
43
|
+
readonly description: 'What an AI agent found on the website of one job (one website, one or more OpenStreetMap elements). See docs/answer-file.md.';
|
|
44
|
+
readonly type: 'object';
|
|
45
|
+
readonly additionalProperties: false;
|
|
46
|
+
readonly required: readonly ['format', 'version', 'ai', 'job', 'elements', 'url', 'fetched_with', 'pages', 'proposals', 'signals'];
|
|
47
|
+
readonly properties: {
|
|
48
|
+
readonly format: {
|
|
49
|
+
readonly const: "osmcorrection-assist-answer";
|
|
50
|
+
};
|
|
51
|
+
readonly version: {
|
|
52
|
+
readonly const: 1;
|
|
53
|
+
};
|
|
54
|
+
readonly ai: {
|
|
55
|
+
readonly type: 'object';
|
|
56
|
+
readonly description: 'The AI that wrote this answer. Used for the changeset tag ai_assisted, after the user confirmed it.';
|
|
57
|
+
readonly additionalProperties: false;
|
|
58
|
+
readonly required: readonly ['vendor', 'model', 'tool'];
|
|
59
|
+
readonly properties: {
|
|
60
|
+
readonly vendor: {
|
|
61
|
+
readonly type: 'string';
|
|
62
|
+
readonly minLength: 1;
|
|
63
|
+
readonly description: 'Maker of the model (not of the tool), e.g. "Anthropic", "OpenAI", "Google", "Amazon".';
|
|
64
|
+
};
|
|
65
|
+
readonly model: {
|
|
66
|
+
readonly type: 'string';
|
|
67
|
+
readonly minLength: 1;
|
|
68
|
+
readonly description: 'The model, e.g. "claude-opus-5-5"; "unknown" if not known.';
|
|
69
|
+
};
|
|
70
|
+
readonly tool: {
|
|
71
|
+
readonly type: 'string';
|
|
72
|
+
readonly minLength: 1;
|
|
73
|
+
readonly description: 'The agent it ran in, e.g. "Claude Code", "Kiro", "Codex CLI".';
|
|
74
|
+
};
|
|
75
|
+
};
|
|
76
|
+
};
|
|
77
|
+
readonly job: {
|
|
78
|
+
readonly type: 'string';
|
|
79
|
+
readonly description: 'ID of the job, e.g. "a1".';
|
|
80
|
+
};
|
|
81
|
+
readonly elements: {
|
|
82
|
+
readonly type: 'array';
|
|
83
|
+
readonly minItems: 1;
|
|
84
|
+
readonly items: {
|
|
85
|
+
readonly type: 'string';
|
|
86
|
+
readonly pattern: '^(node|way|relation)/[0-9]+$';
|
|
87
|
+
};
|
|
88
|
+
readonly description: 'The elements of the job, as in the job list.';
|
|
89
|
+
};
|
|
90
|
+
readonly url: {
|
|
91
|
+
readonly type: 'string';
|
|
92
|
+
readonly description: 'The website from the OpenStreetMap data.';
|
|
93
|
+
};
|
|
94
|
+
readonly fetched_with: {
|
|
95
|
+
readonly enum: readonly ['browser', 'curl'];
|
|
96
|
+
readonly description: 'How the pages were saved.';
|
|
97
|
+
};
|
|
98
|
+
readonly pages: {
|
|
99
|
+
readonly type: 'array';
|
|
100
|
+
readonly description: 'Every page fetched, with "file" (saved) or "error" (could not be fetched).';
|
|
101
|
+
readonly items: {
|
|
102
|
+
readonly type: 'object';
|
|
103
|
+
readonly additionalProperties: false;
|
|
104
|
+
readonly required: readonly ['url'];
|
|
105
|
+
readonly properties: {
|
|
106
|
+
readonly url: {
|
|
107
|
+
readonly type: 'string';
|
|
108
|
+
};
|
|
109
|
+
readonly file: {
|
|
110
|
+
readonly type: 'string';
|
|
111
|
+
readonly pattern: '^seiten/[^/]+\\.html$';
|
|
112
|
+
};
|
|
113
|
+
readonly error: {
|
|
114
|
+
readonly type: 'string';
|
|
115
|
+
};
|
|
116
|
+
};
|
|
117
|
+
readonly oneOf: readonly [{
|
|
118
|
+
readonly required: readonly ['file'];
|
|
119
|
+
}, {
|
|
120
|
+
readonly required: readonly ['error'];
|
|
121
|
+
}];
|
|
122
|
+
};
|
|
123
|
+
};
|
|
124
|
+
readonly proposals: {
|
|
125
|
+
readonly type: 'array';
|
|
126
|
+
readonly items: {
|
|
127
|
+
readonly type: 'object';
|
|
128
|
+
readonly additionalProperties: false;
|
|
129
|
+
readonly required: readonly ['field', 'network', 'value', 'quote', 'source_url'];
|
|
130
|
+
readonly properties: {
|
|
131
|
+
readonly field: {
|
|
132
|
+
readonly type: 'string';
|
|
133
|
+
readonly enum: readonly ["opening_hours", "phone", "fax", "email", "website", "social"];
|
|
134
|
+
};
|
|
135
|
+
readonly network: {
|
|
136
|
+
readonly type: 'string';
|
|
137
|
+
readonly enum: readonly ["none", "facebook", "twitter", "instagram", "tiktok", "youtube", "linkedin", "xing", "pinterest", "vimeo", "flickr", "vk", "mastodon", "bluesky", "mobilizon", "diaspora", "gnusocial", "telegram", "whatsapp", "matrix", "tripadvisor"];
|
|
138
|
+
};
|
|
139
|
+
readonly value: {
|
|
140
|
+
readonly type: 'string';
|
|
141
|
+
};
|
|
142
|
+
readonly quote: {
|
|
143
|
+
readonly type: 'string';
|
|
144
|
+
};
|
|
145
|
+
readonly source_url: {
|
|
146
|
+
readonly type: 'string';
|
|
147
|
+
};
|
|
148
|
+
};
|
|
149
|
+
};
|
|
150
|
+
};
|
|
151
|
+
readonly signals: {
|
|
152
|
+
readonly type: 'array';
|
|
153
|
+
readonly items: {
|
|
154
|
+
readonly type: 'object';
|
|
155
|
+
readonly additionalProperties: false;
|
|
156
|
+
readonly required: readonly ['kind', 'quote', 'source_url'];
|
|
157
|
+
readonly properties: {
|
|
158
|
+
readonly kind: {
|
|
159
|
+
readonly type: 'string';
|
|
160
|
+
readonly enum: readonly ["closed", "moved", "parked", "new_owner"];
|
|
161
|
+
};
|
|
162
|
+
readonly quote: {
|
|
163
|
+
readonly type: 'string';
|
|
164
|
+
};
|
|
165
|
+
readonly source_url: {
|
|
166
|
+
readonly type: 'string';
|
|
167
|
+
};
|
|
168
|
+
};
|
|
169
|
+
};
|
|
170
|
+
};
|
|
171
|
+
};
|
|
172
|
+
};
|
|
173
|
+
/** "node/42" → { type: 'node', id: 42 } */
|
|
174
|
+
export declare function parseElementId(text: string): ElementId | undefined;
|
|
175
|
+
/** Reads an answer file; throws with a reason if it does not have the format. */
|
|
176
|
+
export declare function parseAnswerFile(json: string): AnswerFile;
|
|
177
|
+
/** Normalizes a path inside the working folder ("./seiten\\a1.html" → "seiten/a1.html"). */
|
|
178
|
+
export declare function folderPath(path: string): string;
|
|
179
|
+
/**
|
|
180
|
+
* The reading of one answer file, with the text of its saved pages. A page
|
|
181
|
+
* whose file is missing counts as not fetched, so its quotes are rejected.
|
|
182
|
+
* `vendor` is the maker of the model as confirmed by the user; without it,
|
|
183
|
+
* the one the answer states.
|
|
184
|
+
*/
|
|
185
|
+
export declare function readingFromAnswer(answer: AnswerFile, files: Map<string, string>, vendor?: string): SiteReading;
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
import { isRawAnswer } from './check.js';
|
|
2
|
+
import { htmlToText } from './html.js';
|
|
3
|
+
import { ANSWER_SCHEMA } from './prompt.js';
|
|
4
|
+
import { FIELDS, NETWORKS, SIGNAL_KINDS } from './types.js';
|
|
5
|
+
/*
|
|
6
|
+
* Analysis outside the app by an AI agent on the user's computer, e.g. Claude
|
|
7
|
+
* Code with the user's Claude plan (docs/answer-file.md): the app exports a
|
|
8
|
+
* job file with the element IDs, the agent saves the pages and writes one
|
|
9
|
+
* answer file per job, and the app imports them. The checks are the same as
|
|
10
|
+
* for the API.
|
|
11
|
+
*/
|
|
12
|
+
export const ANSWER_FORMAT = 'osmcorrection-assist-answer';
|
|
13
|
+
/** Where the schema file is published (schema/answer-file.schema.json in this repository). */
|
|
14
|
+
export const ANSWER_FILE_SCHEMA_ID = 'https://raw.githubusercontent.com/cyper85/osmAssist/master/schema/answer-file.schema.json';
|
|
15
|
+
/**
|
|
16
|
+
* JSON Schema of an answer file, given to the model in the job file. The
|
|
17
|
+
* entries of `proposals` and `signals` are the ones of the API (ANSWER_SCHEMA).
|
|
18
|
+
*/
|
|
19
|
+
export const ANSWER_FILE_SCHEMA = {
|
|
20
|
+
$schema: 'https://json-schema.org/draft/2020-12/schema',
|
|
21
|
+
$id: ANSWER_FILE_SCHEMA_ID,
|
|
22
|
+
title: 'osmAssist answer file',
|
|
23
|
+
description: 'What an AI agent found on the website of one job (one website, one or more OpenStreetMap elements). See docs/answer-file.md.',
|
|
24
|
+
type: 'object',
|
|
25
|
+
additionalProperties: false,
|
|
26
|
+
required: ['format', 'version', 'ai', 'job', 'elements', 'url', 'fetched_with', 'pages', 'proposals', 'signals'],
|
|
27
|
+
properties: {
|
|
28
|
+
format: { const: ANSWER_FORMAT },
|
|
29
|
+
version: { const: 1 },
|
|
30
|
+
ai: {
|
|
31
|
+
type: 'object',
|
|
32
|
+
description: 'The AI that wrote this answer. Used for the changeset tag ai_assisted, after the user confirmed it.',
|
|
33
|
+
additionalProperties: false,
|
|
34
|
+
required: ['vendor', 'model', 'tool'],
|
|
35
|
+
properties: {
|
|
36
|
+
vendor: {
|
|
37
|
+
type: 'string',
|
|
38
|
+
minLength: 1,
|
|
39
|
+
description: 'Maker of the model (not of the tool), e.g. "Anthropic", "OpenAI", "Google", "Amazon".',
|
|
40
|
+
},
|
|
41
|
+
model: { type: 'string', minLength: 1, description: 'The model, e.g. "claude-opus-5-5"; "unknown" if not known.' },
|
|
42
|
+
tool: { type: 'string', minLength: 1, description: 'The agent it ran in, e.g. "Claude Code", "Kiro", "Codex CLI".' },
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
job: { type: 'string', description: 'ID of the job, e.g. "a1".' },
|
|
46
|
+
elements: {
|
|
47
|
+
type: 'array',
|
|
48
|
+
minItems: 1,
|
|
49
|
+
items: { type: 'string', pattern: '^(node|way|relation)/[0-9]+$' },
|
|
50
|
+
description: 'The elements of the job, as in the job list.',
|
|
51
|
+
},
|
|
52
|
+
url: { type: 'string', description: 'The website from the OpenStreetMap data.' },
|
|
53
|
+
fetched_with: { enum: ['browser', 'curl'], description: 'How the pages were saved.' },
|
|
54
|
+
pages: {
|
|
55
|
+
type: 'array',
|
|
56
|
+
description: 'Every page fetched, with "file" (saved) or "error" (could not be fetched).',
|
|
57
|
+
items: {
|
|
58
|
+
type: 'object',
|
|
59
|
+
additionalProperties: false,
|
|
60
|
+
required: ['url'],
|
|
61
|
+
properties: {
|
|
62
|
+
url: { type: 'string' },
|
|
63
|
+
file: { type: 'string', pattern: '^seiten/[^/]+\\.html$' },
|
|
64
|
+
error: { type: 'string' },
|
|
65
|
+
},
|
|
66
|
+
oneOf: [{ required: ['file'] }, { required: ['error'] }],
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
proposals: ANSWER_SCHEMA.properties.proposals,
|
|
70
|
+
signals: ANSWER_SCHEMA.properties.signals,
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
const isObject = (value) => typeof value === 'object' && value !== null;
|
|
74
|
+
/** "node/42" → { type: 'node', id: 42 } */
|
|
75
|
+
export function parseElementId(text) {
|
|
76
|
+
const match = /^(node|way|relation)\/(\d+)$/.exec(text.trim());
|
|
77
|
+
return match === null ? undefined : { type: match[1], id: Number(match[2]) };
|
|
78
|
+
}
|
|
79
|
+
/** Reads an answer file; throws with a reason if it does not have the format. */
|
|
80
|
+
export function parseAnswerFile(json) {
|
|
81
|
+
let data;
|
|
82
|
+
try {
|
|
83
|
+
data = JSON.parse(json);
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
throw new Error('not valid JSON');
|
|
87
|
+
}
|
|
88
|
+
if (!isObject(data) || data.format !== ANSWER_FORMAT || data.version !== 1) {
|
|
89
|
+
throw new Error(`not an answer file (format "${ANSWER_FORMAT}", version 1)`);
|
|
90
|
+
}
|
|
91
|
+
if (typeof data.job !== 'string' || typeof data.url !== 'string') {
|
|
92
|
+
throw new Error('"job" or "url" missing');
|
|
93
|
+
}
|
|
94
|
+
const elements = Array.isArray(data.elements)
|
|
95
|
+
? data.elements.map((e) => (typeof e === 'string' ? parseElementId(e) : undefined))
|
|
96
|
+
: [];
|
|
97
|
+
if (elements.length === 0 || elements.some((e) => e === undefined)) {
|
|
98
|
+
throw new Error('"elements" must list IDs like "node/42"');
|
|
99
|
+
}
|
|
100
|
+
const pages = Array.isArray(data.pages) ? data.pages : [];
|
|
101
|
+
const pageOk = (p) => isObject(p) &&
|
|
102
|
+
typeof p.url === 'string' &&
|
|
103
|
+
(p.file === undefined || typeof p.file === 'string') &&
|
|
104
|
+
(p.error === undefined || typeof p.error === 'string');
|
|
105
|
+
if (!pages.every(pageOk)) {
|
|
106
|
+
throw new Error('"pages" must have "url" and "file" or "error"');
|
|
107
|
+
}
|
|
108
|
+
// models sometimes name the kind of a signal "signal" or "type" (first run, 2026-11-02)
|
|
109
|
+
const signals = Array.isArray(data.signals)
|
|
110
|
+
? data.signals.map((s) => (isObject(s) && s.kind === undefined ? { ...s, kind: s.signal ?? s.type } : s))
|
|
111
|
+
: data.signals;
|
|
112
|
+
const answer = {
|
|
113
|
+
proposals: data.proposals,
|
|
114
|
+
signals: Array.isArray(signals)
|
|
115
|
+
? signals.map((s) => (isObject(s) ? { kind: s.kind, quote: s.quote, source_url: s.source_url } : s))
|
|
116
|
+
: signals,
|
|
117
|
+
};
|
|
118
|
+
if (!isRawAnswer(answer)) {
|
|
119
|
+
throw new Error(schemaProblem(answer.proposals, answer.signals));
|
|
120
|
+
}
|
|
121
|
+
const ai = isObject(data.ai) && typeof data.ai.vendor === 'string' && data.ai.vendor.trim() !== '' ? data.ai : undefined;
|
|
122
|
+
return {
|
|
123
|
+
format: ANSWER_FORMAT,
|
|
124
|
+
version: 1,
|
|
125
|
+
...(ai === undefined
|
|
126
|
+
? {}
|
|
127
|
+
: {
|
|
128
|
+
ai: {
|
|
129
|
+
vendor: String(ai.vendor).trim(),
|
|
130
|
+
model: typeof ai.model === 'string' ? ai.model : 'unknown',
|
|
131
|
+
tool: typeof ai.tool === 'string' ? ai.tool : 'unknown',
|
|
132
|
+
},
|
|
133
|
+
}),
|
|
134
|
+
job: data.job,
|
|
135
|
+
elements: elements,
|
|
136
|
+
url: data.url,
|
|
137
|
+
...(typeof data.fetched_with === 'string' ? { fetched_with: data.fetched_with } : {}),
|
|
138
|
+
pages: pages,
|
|
139
|
+
...answer,
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
/** Names the first entry that does not match the schema, e.g. 'signals[0]: "kind" …'. */
|
|
143
|
+
function schemaProblem(proposals, signals) {
|
|
144
|
+
const fields = (entry, rules) => rules.find(([name, ok]) => !ok(entry[name]));
|
|
145
|
+
const text = (v) => typeof v === 'string';
|
|
146
|
+
const listCheck = (name, list, rules) => {
|
|
147
|
+
if (!Array.isArray(list)) {
|
|
148
|
+
return `"${name}" must be a list`;
|
|
149
|
+
}
|
|
150
|
+
for (const [i, entry] of list.entries()) {
|
|
151
|
+
if (!isObject(entry)) {
|
|
152
|
+
return `${name}[${i}] is not an object`;
|
|
153
|
+
}
|
|
154
|
+
const wrong = fields(entry, rules);
|
|
155
|
+
if (wrong !== undefined) {
|
|
156
|
+
return `${name}[${i}]: "${wrong[0]}" ${wrong[2]}`;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
return undefined;
|
|
160
|
+
};
|
|
161
|
+
const oneOf = (list) => (v) => typeof v === 'string' && list.includes(v);
|
|
162
|
+
return (listCheck('proposals', proposals, [
|
|
163
|
+
['field', oneOf(FIELDS), `must be one of ${FIELDS.join(', ')}`],
|
|
164
|
+
['network', oneOf(['none', ...NETWORKS]), 'must be "none" or a network'],
|
|
165
|
+
['value', text, 'must be a text'],
|
|
166
|
+
['quote', text, 'must be a text'],
|
|
167
|
+
['source_url', text, 'must be a text'],
|
|
168
|
+
]) ??
|
|
169
|
+
listCheck('signals', signals, [
|
|
170
|
+
['kind', oneOf(SIGNAL_KINDS), `must be one of ${SIGNAL_KINDS.join(', ')}`],
|
|
171
|
+
['quote', text, 'must be a text'],
|
|
172
|
+
['source_url', text, 'must be a text'],
|
|
173
|
+
]) ??
|
|
174
|
+
'"proposals" or "signals" do not match the schema');
|
|
175
|
+
}
|
|
176
|
+
/** Normalizes a path inside the working folder ("./seiten\\a1.html" → "seiten/a1.html"). */
|
|
177
|
+
export function folderPath(path) {
|
|
178
|
+
return path.replace(/\\/g, '/').replace(/^(\.\/)+/, '');
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* The reading of one answer file, with the text of its saved pages. A page
|
|
182
|
+
* whose file is missing counts as not fetched, so its quotes are rejected.
|
|
183
|
+
* `vendor` is the maker of the model as confirmed by the user; without it,
|
|
184
|
+
* the one the answer states.
|
|
185
|
+
*/
|
|
186
|
+
export function readingFromAnswer(answer, files, vendor) {
|
|
187
|
+
const texts = {};
|
|
188
|
+
const pages = answer.pages.map((page) => {
|
|
189
|
+
if (page.file === undefined) {
|
|
190
|
+
return { url: page.url, error: page.error ?? 'not fetched' };
|
|
191
|
+
}
|
|
192
|
+
const html = files.get(folderPath(page.file));
|
|
193
|
+
if (html === undefined) {
|
|
194
|
+
return { url: page.url, error: `file missing: ${page.file}` };
|
|
195
|
+
}
|
|
196
|
+
if (html.trim() === '') {
|
|
197
|
+
return { url: page.url, error: `file empty: ${page.file}` };
|
|
198
|
+
}
|
|
199
|
+
texts[page.url] = htmlToText(html, page.url);
|
|
200
|
+
return { url: page.url };
|
|
201
|
+
});
|
|
202
|
+
return {
|
|
203
|
+
url: answer.url,
|
|
204
|
+
answer: { proposals: answer.proposals, signals: answer.signals },
|
|
205
|
+
texts,
|
|
206
|
+
pages,
|
|
207
|
+
model: {
|
|
208
|
+
vendor: vendor ?? answer.ai?.vendor ?? 'unknown',
|
|
209
|
+
name: answer.ai === undefined ? 'unknown' : `${answer.ai.model} (${answer.ai.tool})`,
|
|
210
|
+
id: answer.ai?.model ?? 'unknown',
|
|
211
|
+
},
|
|
212
|
+
usage: { inputTokens: 0, outputTokens: 0 },
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
//# sourceMappingURL=answerFile.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"answerFile.js","sourceRoot":"","sources":["../src/answerFile.ts"],"names":[],"mappings":"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"}
|
package/dist/check.d.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { type AssistRequest, type AssistResult, type Proposal, type RawAnswer, type Rejected, type Signal, type SiteReading, type Compare, type Validate } from './types.js';
|
|
2
|
+
/** Checks the structure of the answer; the schema should already guarantee it. */
|
|
3
|
+
export declare function isRawAnswer(value: unknown): value is RawAnswer;
|
|
4
|
+
/**
|
|
5
|
+
* The key for a suggestion in the form the element already uses: an existing
|
|
6
|
+
* `contact:<name>`, else an existing `<name>`, else `contact:<name>`. The
|
|
7
|
+
* website stays `website` if it is new, opening hours are always
|
|
8
|
+
* `opening_hours`.
|
|
9
|
+
*/
|
|
10
|
+
export declare function targetKey(base: string, tags: Record<string, string>): string;
|
|
11
|
+
/**
|
|
12
|
+
* Turns the answer into suggestions. A suggestion is rejected if its quote is
|
|
13
|
+
* not on a fetched page of the site, if a link or address is not on the page,
|
|
14
|
+
* if the value is invalid, or if it is the current value.
|
|
15
|
+
*/
|
|
16
|
+
export declare function checkAnswer(answer: RawAnswer, pages: Map<string, string>, request: AssistRequest, validate?: Validate, compare?: Compare): {
|
|
17
|
+
proposals: Proposal[];
|
|
18
|
+
signals: Signal[];
|
|
19
|
+
rejected: Rejected[];
|
|
20
|
+
};
|
|
21
|
+
/** Compares what was read on the website with the tags of one element. Costs nothing. */
|
|
22
|
+
export declare function assess(reading: SiteReading, request: AssistRequest, validate?: Validate, compare?: Compare): AssistResult;
|