@useinvio/nfse-sdk 3.0.0 → 3.2.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.
Files changed (120) hide show
  1. package/CHANGELOG.md +176 -0
  2. package/README.en.md +146 -0
  3. package/README.md +77 -518
  4. package/dist/agent.d.ts +131 -0
  5. package/dist/agent.js +551 -0
  6. package/dist/agent.js.map +1 -0
  7. package/dist/certificate.d.ts +96 -0
  8. package/dist/certificate.js +218 -0
  9. package/dist/certificate.js.map +1 -0
  10. package/dist/certificateValidation.d.ts +11 -6
  11. package/dist/certificateValidation.js +23 -12
  12. package/dist/certificateValidation.js.map +1 -1
  13. package/dist/cli.d.ts +2 -0
  14. package/dist/cli.js +520 -0
  15. package/dist/cli.js.map +1 -0
  16. package/dist/cliBin.d.ts +2 -0
  17. package/dist/cliBin.js +6 -0
  18. package/dist/cliBin.js.map +1 -0
  19. package/dist/cliExample.d.ts +6 -0
  20. package/dist/cliExample.js +29 -0
  21. package/dist/cliExample.js.map +1 -0
  22. package/dist/client.d.ts +51 -4
  23. package/dist/client.js +41 -10
  24. package/dist/client.js.map +1 -1
  25. package/dist/config.d.ts +3 -0
  26. package/dist/config.js +8 -0
  27. package/dist/config.js.map +1 -1
  28. package/dist/consultas.d.ts +34 -0
  29. package/dist/consultas.js +61 -0
  30. package/dist/consultas.js.map +1 -0
  31. package/dist/dpsJson.js +1 -41
  32. package/dist/dpsJson.js.map +1 -1
  33. package/dist/dpsJsonSchema.d.ts +17 -0
  34. package/dist/dpsJsonSchema.js +184 -0
  35. package/dist/dpsJsonSchema.js.map +1 -0
  36. package/dist/emissaoNota.d.ts +11 -6
  37. package/dist/emissaoNota.js +10 -6
  38. package/dist/emissaoNota.js.map +1 -1
  39. package/dist/eventoJson.d.ts +76 -0
  40. package/dist/eventoJson.js +128 -0
  41. package/dist/eventoJson.js.map +1 -0
  42. package/dist/eventosNfse.d.ts +38 -0
  43. package/dist/eventosNfse.js +73 -0
  44. package/dist/eventosNfse.js.map +1 -0
  45. package/dist/fiscalDomains.d.ts +22 -0
  46. package/dist/fiscalDomains.js +99 -0
  47. package/dist/fiscalDomains.js.map +1 -0
  48. package/dist/fiscalValidation.js +21 -53
  49. package/dist/fiscalValidation.js.map +1 -1
  50. package/dist/gzipB64.d.ts +5 -0
  51. package/dist/gzipB64.js +12 -0
  52. package/dist/gzipB64.js.map +1 -1
  53. package/dist/index.d.ts +22 -7
  54. package/dist/index.js +11 -4
  55. package/dist/index.js.map +1 -1
  56. package/dist/jsonSchemaCheck.d.ts +12 -0
  57. package/dist/jsonSchemaCheck.js +70 -0
  58. package/dist/jsonSchemaCheck.js.map +1 -0
  59. package/dist/loadPfx.d.ts +2 -0
  60. package/dist/loadPfx.js +2 -1
  61. package/dist/loadPfx.js.map +1 -1
  62. package/dist/mcpCli.d.ts +2 -0
  63. package/dist/mcpCli.js +7 -0
  64. package/dist/mcpCli.js.map +1 -0
  65. package/dist/mcpServer.d.ts +49 -0
  66. package/dist/mcpServer.js +159 -0
  67. package/dist/mcpServer.js.map +1 -0
  68. package/dist/nfseXml.d.ts +48 -0
  69. package/dist/nfseXml.js +86 -0
  70. package/dist/nfseXml.js.map +1 -0
  71. package/dist/rejeicoes.d.ts +50 -0
  72. package/dist/rejeicoes.js +62 -0
  73. package/dist/rejeicoes.js.map +1 -0
  74. package/dist/rejeicoesCatalog.d.ts +10 -0
  75. package/dist/rejeicoesCatalog.js +4449 -0
  76. package/dist/rejeicoesCatalog.js.map +1 -0
  77. package/dist/sefinClient.d.ts +107 -7
  78. package/dist/sefinClient.js +232 -22
  79. package/dist/sefinClient.js.map +1 -1
  80. package/dist/signXml.d.ts +3 -3
  81. package/dist/signXml.js +17 -7
  82. package/dist/signXml.js.map +1 -1
  83. package/dist/testing.d.ts +80 -0
  84. package/dist/testing.js +285 -0
  85. package/dist/testing.js.map +1 -0
  86. package/dist/xmlBuild.d.ts +11 -0
  87. package/dist/xmlBuild.js +44 -0
  88. package/dist/xmlBuild.js.map +1 -0
  89. package/dist/xmlInspection.d.ts +1 -1
  90. package/dist/xmlInspection.js +10 -9
  91. package/dist/xmlInspection.js.map +1 -1
  92. package/dist/xsdValidation.d.ts +24 -5
  93. package/dist/xsdValidation.js +97 -67
  94. package/dist/xsdValidation.js.map +1 -1
  95. package/docs/README.md +47 -0
  96. package/docs/REGRAS_NEGOCIO_EMISSAO.md +8 -8
  97. package/docs/conceitos/como-funciona.md +87 -0
  98. package/docs/guias/adn.md +51 -0
  99. package/docs/guias/agentes-ia.md +111 -0
  100. package/docs/guias/cancelar.md +70 -0
  101. package/docs/guias/certificado.md +88 -0
  102. package/docs/guias/cli.md +68 -0
  103. package/docs/guias/consultar.md +54 -0
  104. package/docs/guias/emitir.md +172 -0
  105. package/docs/guias/recuperar-emissao.md +33 -0
  106. package/docs/guias/rejeicoes.md +70 -0
  107. package/docs/guias/testes.md +79 -0
  108. package/docs/guias/timeouts.md +33 -0
  109. package/docs/referencia/ambientes.md +35 -0
  110. package/docs/referencia/api.md +137 -0
  111. package/docs/referencia/erros.md +55 -0
  112. package/docs/tutoriais/primeira-nfse.md +130 -0
  113. package/examples/README.md +3 -1
  114. package/examples/cancel-nfse.ts +29 -0
  115. package/examples/client-resource.ts +3 -5
  116. package/examples/emit-homologation.ts +3 -4
  117. package/examples/handle-errors.ts +3 -3
  118. package/examples/recover-emission.ts +29 -0
  119. package/llms.txt +97 -82
  120. package/package.json +31 -6
@@ -0,0 +1,131 @@
1
+ import type { NfseResumo } from './nfseXml.js';
2
+ import { type ExplicacaoRejeicao } from './rejeicoes.js';
3
+ import { type JsonSchema } from './dpsJsonSchema.js';
4
+ import type { Ambiente } from './config.js';
5
+ import type { DpsJsonRequest } from './dpsJson.js';
6
+ import type { CertificateInput } from './certificate.js';
7
+ export { DPS_JSON_REQUEST_SCHEMA } from './dpsJsonSchema.js';
8
+ export type { JsonSchema } from './dpsJsonSchema.js';
9
+ export type NfseAgentIssueSource = 'sdk' | 'invio' | 'schema' | 'xsd' | 'sefin' | 'certificate' | 'environment';
10
+ /** One shape for validation, XSD, certificate and SEFIN problems. */
11
+ export interface NfseAgentIssue {
12
+ code: string;
13
+ /** Dotted path inside the request (`emissao.` omitted, as in `DpsValidationIssue`); `$` when global. */
14
+ path: string;
15
+ severity: 'error' | 'warning';
16
+ source: NfseAgentIssueSource;
17
+ message: string;
18
+ suggestion?: string;
19
+ }
20
+ export interface NfseAgentContext {
21
+ /** Host-owned A1 certificate (`NfseCertificate` recommended). Required only by tools that talk to SEFIN. */
22
+ pfx?: CertificateInput;
23
+ /** Host-owned environment. Defaults to `restrita`; tool input cannot override it. */
24
+ ambiente?: Ambiente;
25
+ }
26
+ /** Same hints as MCP `ToolAnnotations`. */
27
+ export interface NfseAgentToolAnnotations {
28
+ readOnlyHint: boolean;
29
+ destructiveHint: boolean;
30
+ idempotentHint: boolean;
31
+ openWorldHint: boolean;
32
+ }
33
+ export interface NfseAgentTool<Input = any, Output = any> {
34
+ name: string;
35
+ title: string;
36
+ description: string;
37
+ inputSchema: JsonSchema;
38
+ outputSchema: JsonSchema;
39
+ annotations: NfseAgentToolAnnotations;
40
+ requiresCertificate: boolean;
41
+ /**
42
+ * Expected failures (invalid input, rejection, missing certificate) resolve
43
+ * with `issues`. Infrastructure failures (XSD engine, network) reject.
44
+ */
45
+ run(input: Input, context?: NfseAgentContext): Promise<Output>;
46
+ }
47
+ export interface DpsRequestToolInput {
48
+ request: Omit<DpsJsonRequest, 'ambiente'>;
49
+ }
50
+ export interface ValidateDpsToolOutput {
51
+ valid: boolean;
52
+ schemaVersion: string;
53
+ ambiente: Ambiente;
54
+ issues: NfseAgentIssue[];
55
+ warnings: NfseAgentIssue[];
56
+ }
57
+ export interface BuildDpsXmlToolOutput {
58
+ ok: boolean;
59
+ ambiente: Ambiente;
60
+ dpsId?: string;
61
+ xml?: string;
62
+ issues: NfseAgentIssue[];
63
+ warnings: NfseAgentIssue[];
64
+ }
65
+ export interface GetNfseToolInput {
66
+ chaveAcesso: string;
67
+ }
68
+ export interface GetNfseToolOutput {
69
+ ok: boolean;
70
+ ambiente: Ambiente;
71
+ chaveAcesso: string;
72
+ status?: number;
73
+ nfseXml?: string;
74
+ body?: unknown;
75
+ issues: NfseAgentIssue[];
76
+ }
77
+ /**
78
+ * Converts known SDK errors to `NfseAgentIssue[]`. Returns `undefined` for
79
+ * anything else (transport, XSD engine, programming errors) so the caller can rethrow.
80
+ */
81
+ export declare function toAgentIssues(error: unknown): NfseAgentIssue[] | undefined;
82
+ /** `DPS_JSON_REQUEST_SCHEMA` without `ambiente`, which belongs to the host. */
83
+ export declare const DPS_AGENT_REQUEST_SCHEMA: JsonSchema;
84
+ export declare const validateDpsTool: NfseAgentTool<DpsRequestToolInput, ValidateDpsToolOutput>;
85
+ export declare const buildDpsXmlTool: NfseAgentTool<DpsRequestToolInput, BuildDpsXmlToolOutput>;
86
+ export declare const getNfseTool: NfseAgentTool<GetNfseToolInput, GetNfseToolOutput>;
87
+ export interface ExplainRejectionToolInput {
88
+ codigos: string[];
89
+ }
90
+ export interface ExplainRejectionToolOutput {
91
+ explicacoes: ExplicacaoRejeicao[];
92
+ }
93
+ export declare const explainRejectionTool: NfseAgentTool<ExplainRejectionToolInput, ExplainRejectionToolOutput>;
94
+ export interface RecoverNfseToolInput {
95
+ dpsId: string;
96
+ }
97
+ export interface RecoverNfseToolOutput {
98
+ ok: boolean;
99
+ ambiente: Ambiente;
100
+ dpsId: string;
101
+ /** True when SEFIN has an NFS-e for this DPS. */
102
+ encontrada?: boolean;
103
+ chaveAcesso?: string;
104
+ resumo?: NfseResumo;
105
+ issues: NfseAgentIssue[];
106
+ }
107
+ export declare const recoverNfseByDpsTool: NfseAgentTool<RecoverNfseToolInput, RecoverNfseToolOutput>;
108
+ /** Read-only tools: no tool here sends a document to SEFIN. */
109
+ export declare const nfseAgentTools: readonly NfseAgentTool[];
110
+ export interface NfseAgentToolkitOptions {
111
+ /**
112
+ * Enables `prepare_*` / `confirm_*` tools that emit and cancel NFS-e. Off by
113
+ * default. Even when enabled, nothing is sent until the model calls
114
+ * `confirm_*` with the id returned by `prepare_*`, which the model must only
115
+ * do after the user approves the summary.
116
+ */
117
+ allowWrite?: boolean;
118
+ /** How long a prepared emission/cancellation can be confirmed. Default: 10 minutes. */
119
+ confirmationTtlMs?: number;
120
+ }
121
+ export interface NfseAgentToolkit {
122
+ tools: readonly NfseAgentTool[];
123
+ /** Number of prepared operations still waiting for confirmation. */
124
+ pendingCount(): number;
125
+ }
126
+ /**
127
+ * Builds the tool list for an agent session. Read-only tools are always
128
+ * included; emission and cancellation are added only with `allowWrite: true`
129
+ * and always take two steps (prepare, then confirm after user approval).
130
+ */
131
+ export declare function createNfseAgentToolkit(options?: NfseAgentToolkitOptions): NfseAgentToolkit;
package/dist/agent.js ADDED
@@ -0,0 +1,551 @@
1
+ /**
2
+ * Framework-neutral tool definitions for MCP servers and LLM agents.
3
+ *
4
+ * Each tool carries a JSON Schema for input and output, MCP-style annotations
5
+ * and a `run` handler. Adapters (MCP, OpenAI, LangChain, Vercel AI SDK) only
6
+ * need to map these fields. Certificate and environment are host
7
+ * configuration passed in `NfseAgentContext`, never tool input, so a model
8
+ * cannot select production or see key material.
9
+ */
10
+ import { randomUUID } from 'node:crypto';
11
+ import { CertificateValidationError } from './certificateValidation.js';
12
+ import { NfseCertificate } from './certificate.js';
13
+ import { ConsultaNfseError, obterNfse, recuperarNfsePorDps } from './consultas.js';
14
+ import { prepararNota, transmitirNotaPreparada } from './emissaoNota.js';
15
+ import { buildCancelamentoFromJson, EventoInvalidoError } from './eventoJson.js';
16
+ import { registrarEvento, RegistrarEventoError } from './eventosNfse.js';
17
+ import { explicarRejeicao } from './rejeicoes.js';
18
+ import { DEFAULT_AMBIENTE } from './config.js';
19
+ import { buildDpsFromJson } from './dpsJson.js';
20
+ import { DPS_JSON_REQUEST_SCHEMA } from './dpsJsonSchema.js';
21
+ import { EmitirNotaError } from './emissaoNota.js';
22
+ import { DpsFiscalValidationError, validateDpsJsonRequest } from './fiscalValidation.js';
23
+ import { gunzipBase64 } from './gzipB64.js';
24
+ import { checkJsonSchema } from './jsonSchemaCheck.js';
25
+ import { consultarNfse, extrairErros } from './sefinClient.js';
26
+ import { DpsXsdValidationError, validateDpsXmlAgainstXsd } from './xsdValidation.js';
27
+ export { DPS_JSON_REQUEST_SCHEMA } from './dpsJsonSchema.js';
28
+ function fromValidationIssue(issue) {
29
+ return { ...issue };
30
+ }
31
+ function sefinIssue(erro) {
32
+ const authorization = erro.Codigo === 'INVALID_AUTHORIZATION_RESPONSE';
33
+ const official = authorization ? undefined : explicarRejeicao(erro);
34
+ const suggestion = authorization
35
+ ? 'Consulte a nota pela chave ou concilie a DPS antes de reenviar.'
36
+ : official?.conhecida
37
+ ? [official.campo && `Campo: ${official.campo}.`, official.regra && `Regra oficial: ${official.regra.slice(0, 300)}`].filter(Boolean).join(' ')
38
+ : undefined;
39
+ return {
40
+ code: erro.Codigo,
41
+ path: '$',
42
+ severity: 'error',
43
+ source: authorization ? 'sdk' : 'sefin',
44
+ message: erro.Complemento ? `${erro.Descricao} (${erro.Complemento})` : erro.Descricao,
45
+ ...(suggestion ? { suggestion } : {}),
46
+ };
47
+ }
48
+ /**
49
+ * Converts known SDK errors to `NfseAgentIssue[]`. Returns `undefined` for
50
+ * anything else (transport, XSD engine, programming errors) so the caller can rethrow.
51
+ */
52
+ export function toAgentIssues(error) {
53
+ if (error instanceof DpsFiscalValidationError)
54
+ return error.issues.map(fromValidationIssue);
55
+ if (error instanceof DpsXsdValidationError) {
56
+ return error.details.map((message) => ({ code: 'XSD_INVALID', path: '$', severity: 'error', source: 'xsd', message }));
57
+ }
58
+ if (error instanceof CertificateValidationError) {
59
+ return [{ code: error.code, path: '$', severity: 'error', source: 'certificate', message: error.message }];
60
+ }
61
+ if (error instanceof EmitirNotaError || error instanceof RegistrarEventoError || error instanceof ConsultaNfseError) {
62
+ const issues = error.erros.map(sefinIssue);
63
+ return issues.length ? issues : [{ code: 'SEFIN_REJECTED', path: '$', severity: 'error', source: 'sefin', message: error.message }];
64
+ }
65
+ if (error instanceof EventoInvalidoError) {
66
+ return [{ code: error.code, path: error.field, severity: 'error', source: 'sdk', message: error.message }];
67
+ }
68
+ return undefined;
69
+ }
70
+ function resolveAmbiente(context) {
71
+ return context?.ambiente ?? DEFAULT_AMBIENTE;
72
+ }
73
+ function stripEmissao(path) {
74
+ return path.replace(/^emissao\./, '');
75
+ }
76
+ /** Fields the schema does not accept are reported instead of silently ignored by the builder. */
77
+ function unsupportedFieldIssues(request) {
78
+ return checkJsonSchema(DPS_JSON_REQUEST_SCHEMA, request)
79
+ .filter(({ keyword }) => keyword === 'additionalProperties')
80
+ .map(({ path }) => ({
81
+ code: 'UNSUPPORTED_FIELD',
82
+ path: stripEmissao(path),
83
+ severity: 'error',
84
+ source: 'schema',
85
+ message: `${path} nao e um campo suportado pelo schema da DPS`,
86
+ suggestion: 'Remova o campo ou use o nome canonico descrito no schema.',
87
+ }));
88
+ }
89
+ function environmentIssue(request, ambiente) {
90
+ const requested = request?.ambiente;
91
+ if (requested === undefined || requested === ambiente)
92
+ return [];
93
+ return [{
94
+ code: 'AMBIENTE_NOT_ALLOWED',
95
+ path: 'ambiente',
96
+ severity: 'error',
97
+ source: 'environment',
98
+ message: `O ambiente e definido pelo host (${ambiente}); a requisicao pediu ${String(requested)}.`,
99
+ suggestion: 'Remova o campo ambiente da requisicao.',
100
+ }];
101
+ }
102
+ function validateForAgent(input, context) {
103
+ const ambiente = resolveAmbiente(context);
104
+ const request = input?.request;
105
+ const hostIssues = environmentIssue(request, ambiente);
106
+ const withAmbiente = request && typeof request === 'object' && !Array.isArray(request) ? { ...request, ambiente } : request;
107
+ const report = validateDpsJsonRequest(withAmbiente);
108
+ const structural = report.issues.some((issue) => issue.code === 'INVALID_STRUCTURE');
109
+ const issues = [
110
+ ...hostIssues,
111
+ ...(structural ? [] : unsupportedFieldIssues(withAmbiente)),
112
+ ...report.issues.map(fromValidationIssue),
113
+ ];
114
+ return { ambiente, report, issues, warnings: report.warnings.map(fromValidationIssue), request: withAmbiente };
115
+ }
116
+ const { $schema: _draft, ...requestSchema } = DPS_JSON_REQUEST_SCHEMA;
117
+ const { ambiente: _ambiente, ...requestProperties } = requestSchema.properties;
118
+ /** `DPS_JSON_REQUEST_SCHEMA` without `ambiente`, which belongs to the host. */
119
+ export const DPS_AGENT_REQUEST_SCHEMA = { ...requestSchema, properties: requestProperties };
120
+ const DPS_REQUEST_INPUT_SCHEMA = {
121
+ type: 'object',
122
+ properties: { request: DPS_AGENT_REQUEST_SCHEMA },
123
+ required: ['request'],
124
+ additionalProperties: false,
125
+ };
126
+ const ISSUE_SCHEMA = {
127
+ type: 'object',
128
+ properties: {
129
+ code: { type: 'string', description: 'Codigo estavel do problema, ex.: TOT_TRIB_REQUIRED ou codigo SEFIN.' },
130
+ path: { type: 'string', description: 'Campo afetado; $ quando global.' },
131
+ severity: { type: 'string', enum: ['error', 'warning'] },
132
+ source: { type: 'string', enum: ['sdk', 'invio', 'schema', 'xsd', 'sefin', 'certificate', 'environment'] },
133
+ message: { type: 'string' },
134
+ suggestion: { type: 'string' },
135
+ },
136
+ required: ['code', 'path', 'severity', 'source', 'message'],
137
+ additionalProperties: false,
138
+ };
139
+ const ISSUES = { type: 'array', items: ISSUE_SCHEMA };
140
+ const AMBIENTE = { type: 'string', enum: ['restrita', 'producao'] };
141
+ export const validateDpsTool = {
142
+ name: 'validate_dps',
143
+ title: 'Validar DPS da NFS-e',
144
+ description: 'Valida uma requisicao de DPS (NFS-e Nacional) sem certificado, sem rede e sem efeitos colaterais. ' +
145
+ 'Retorna issues com code, path, message e suggestion; corrija os campos indicados e valide novamente ate valid=true. ' +
146
+ 'Nao invente codigos fiscais (cTribNac, regime, tributacao): pergunte ao usuario quando faltarem.',
147
+ inputSchema: DPS_REQUEST_INPUT_SCHEMA,
148
+ outputSchema: {
149
+ type: 'object',
150
+ properties: { valid: { type: 'boolean' }, schemaVersion: { type: 'string' }, ambiente: AMBIENTE, issues: ISSUES, warnings: ISSUES },
151
+ required: ['valid', 'schemaVersion', 'ambiente', 'issues', 'warnings'],
152
+ additionalProperties: false,
153
+ },
154
+ annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
155
+ requiresCertificate: false,
156
+ async run(input, context) {
157
+ const { ambiente, report, issues, warnings } = validateForAgent(input, context);
158
+ return { valid: issues.length === 0, schemaVersion: report.schemaVersion, ambiente, issues, warnings };
159
+ },
160
+ };
161
+ export const buildDpsXmlTool = {
162
+ name: 'build_dps_xml',
163
+ title: 'Gerar XML da DPS',
164
+ description: 'Valida a requisicao e gera o XML nao assinado da DPS, conferido contra o XSD oficial v1.01. ' +
165
+ 'Nao assina nem transmite. Use para revisar a nota antes de pedir a emissao ao usuario.',
166
+ inputSchema: DPS_REQUEST_INPUT_SCHEMA,
167
+ outputSchema: {
168
+ type: 'object',
169
+ properties: {
170
+ ok: { type: 'boolean' },
171
+ ambiente: AMBIENTE,
172
+ dpsId: { type: 'string', description: 'Id de 45 caracteres da DPS.' },
173
+ xml: { type: 'string', description: 'XML da DPS nao assinado.' },
174
+ issues: ISSUES,
175
+ warnings: ISSUES,
176
+ },
177
+ required: ['ok', 'ambiente', 'issues', 'warnings'],
178
+ additionalProperties: false,
179
+ },
180
+ annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
181
+ requiresCertificate: false,
182
+ async run(input, context) {
183
+ const { ambiente, issues, warnings, request } = validateForAgent(input, context);
184
+ if (issues.length)
185
+ return { ok: false, ambiente, issues, warnings };
186
+ try {
187
+ const built = buildDpsFromJson(request);
188
+ validateDpsXmlAgainstXsd(built.xml);
189
+ return { ok: true, ambiente, dpsId: built.id, xml: built.xml, issues: [], warnings };
190
+ }
191
+ catch (error) {
192
+ const mapped = toAgentIssues(error);
193
+ if (!mapped)
194
+ throw error;
195
+ return { ok: false, ambiente, issues: mapped, warnings };
196
+ }
197
+ },
198
+ };
199
+ export const getNfseTool = {
200
+ name: 'get_nfse',
201
+ title: 'Consultar NFS-e',
202
+ description: 'Consulta uma NFS-e na SEFIN pela chave de acesso de 50 digitos, no ambiente configurado pelo host. ' +
203
+ 'Use tambem para conciliar uma emissao com resposta ambigua antes de qualquer reenvio.',
204
+ inputSchema: {
205
+ type: 'object',
206
+ properties: { chaveAcesso: { type: 'string', pattern: '^\\d{50}$', description: 'Chave de acesso da NFS-e, 50 digitos.' } },
207
+ required: ['chaveAcesso'],
208
+ additionalProperties: false,
209
+ },
210
+ outputSchema: {
211
+ type: 'object',
212
+ properties: {
213
+ ok: { type: 'boolean' },
214
+ ambiente: AMBIENTE,
215
+ chaveAcesso: { type: 'string' },
216
+ status: { type: 'number', description: 'Status HTTP da SEFIN.' },
217
+ nfseXml: { type: 'string', description: 'XML da NFS-e quando retornado.' },
218
+ body: { description: 'Corpo da resposta da SEFIN, sem o XML compactado.' },
219
+ issues: ISSUES,
220
+ },
221
+ required: ['ok', 'ambiente', 'chaveAcesso', 'issues'],
222
+ additionalProperties: false,
223
+ },
224
+ annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true },
225
+ requiresCertificate: true,
226
+ async run(input, context) {
227
+ const ambiente = resolveAmbiente(context);
228
+ const chaveAcesso = String(input?.chaveAcesso ?? '');
229
+ const fail = (issue) => ({ ok: false, ambiente, chaveAcesso, issues: [issue] });
230
+ if (!/^\d{50}$/.test(chaveAcesso)) {
231
+ return fail({ code: 'CHAVE_ACESSO_INVALID_FORMAT', path: 'chaveAcesso', severity: 'error', source: 'sdk', message: 'chaveAcesso deve ter 50 digitos numericos.' });
232
+ }
233
+ if (!context?.pfx) {
234
+ return fail({ code: 'CERTIFICATE_REQUIRED', path: '$', severity: 'error', source: 'certificate', message: 'O host nao configurou um certificado A1 para consultar a SEFIN.' });
235
+ }
236
+ const { status, body } = await consultarNfse(chaveAcesso, context.pfx, ambiente);
237
+ const { nfseXmlGZipB64, NfseXmlGZipB64, ...rest } = (body && typeof body === 'object' && !Array.isArray(body) ? body : { value: body });
238
+ const compressed = nfseXmlGZipB64 ?? NfseXmlGZipB64;
239
+ if (status < 200 || status >= 300) {
240
+ const issues = extrairErros(body).map(sefinIssue);
241
+ return { ok: false, ambiente, chaveAcesso, status, body: rest, issues: issues.length ? issues : [
242
+ { code: `HTTP_${status}`, path: '$', severity: 'error', source: 'sefin', message: `A SEFIN respondeu HTTP ${status}.` },
243
+ ] };
244
+ }
245
+ const nfseXml = typeof compressed === 'string' ? gunzipBase64(compressed) : undefined;
246
+ return { ok: true, ambiente, chaveAcesso, status, ...(nfseXml ? { nfseXml } : {}), body: rest, issues: [] };
247
+ },
248
+ };
249
+ export const explainRejectionTool = {
250
+ name: 'explain_rejection',
251
+ title: 'Explicar codigo de rejeicao da SEFIN',
252
+ description: 'Explica codigos de rejeicao da NFS-e Nacional (ex.: E0014) com a mensagem, a regra e o campo oficiais, ' +
253
+ 'extraidos dos anexos publicados em gov.br/nfse. Sem rede e sem certificado. Use apos uma rejeicao para ' +
254
+ 'explicar ao usuario o que corrigir. Se conhecida=false, o codigo nao esta no catalogo: nao invente uma causa.',
255
+ inputSchema: {
256
+ type: 'object',
257
+ properties: {
258
+ codigos: {
259
+ type: 'array',
260
+ items: { type: 'string', pattern: '^[Ee]?\\d{4}$|^[A-Z_]+$' },
261
+ description: 'De 1 a 20 codigos retornados pela SEFIN, ex.: ["E0014"].',
262
+ },
263
+ },
264
+ required: ['codigos'],
265
+ additionalProperties: false,
266
+ },
267
+ outputSchema: {
268
+ type: 'object',
269
+ properties: { explicacoes: { type: 'array', items: { type: 'object' } } },
270
+ required: ['explicacoes'],
271
+ additionalProperties: false,
272
+ },
273
+ annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
274
+ requiresCertificate: false,
275
+ async run(input) {
276
+ const codigos = Array.isArray(input?.codigos) ? input.codigos.slice(0, 20) : [];
277
+ return { explicacoes: codigos.map((codigo) => explicarRejeicao(String(codigo))) };
278
+ },
279
+ };
280
+ function certificateRequired() {
281
+ return { code: 'CERTIFICATE_REQUIRED', path: '$', severity: 'error', source: 'certificate', message: 'O host nao configurou um certificado A1 para falar com a SEFIN.' };
282
+ }
283
+ export const recoverNfseByDpsTool = {
284
+ name: 'recover_nfse_by_dps',
285
+ title: 'Recuperar NFS-e pelo Id da DPS',
286
+ description: 'Descobre se a SEFIN gerou uma NFS-e para um Id de DPS (45 caracteres, "DPS" + 42 digitos). ' +
287
+ 'Use depois de um timeout ou resposta ambigua na emissao, antes de qualquer reenvio. ' +
288
+ 'encontrada=false significa que a DPS nao chegou a SEFIN.',
289
+ inputSchema: {
290
+ type: 'object',
291
+ properties: { dpsId: { type: 'string', pattern: '^DPS\\d{42}$', description: 'Id da DPS.' } },
292
+ required: ['dpsId'],
293
+ additionalProperties: false,
294
+ },
295
+ outputSchema: { type: 'object', properties: { ok: { type: 'boolean' } }, required: ['ok'] },
296
+ annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true },
297
+ requiresCertificate: true,
298
+ async run(input, context) {
299
+ const ambiente = resolveAmbiente(context);
300
+ const dpsId = String(input?.dpsId ?? '');
301
+ if (!/^DPS\d{42}$/.test(dpsId)) {
302
+ return { ok: false, ambiente, dpsId, issues: [{ code: 'SEFIN_PARAMETER_INVALID', path: 'dpsId', severity: 'error', source: 'sdk', message: 'dpsId deve ser "DPS" seguido de 42 digitos.' }] };
303
+ }
304
+ if (!context?.pfx)
305
+ return { ok: false, ambiente, dpsId, issues: [certificateRequired()] };
306
+ try {
307
+ const nota = await recuperarNfsePorDps(dpsId, context.pfx, ambiente);
308
+ if (!nota)
309
+ return { ok: true, ambiente, dpsId, encontrada: false, issues: [] };
310
+ return { ok: true, ambiente, dpsId, encontrada: true, chaveAcesso: nota.chaveAcesso, resumo: nota.resumo, issues: [] };
311
+ }
312
+ catch (error) {
313
+ const issues = toAgentIssues(error);
314
+ if (!issues)
315
+ throw error;
316
+ return { ok: false, ambiente, dpsId, issues };
317
+ }
318
+ },
319
+ };
320
+ /** Read-only tools: no tool here sends a document to SEFIN. */
321
+ export const nfseAgentTools = [
322
+ validateDpsTool,
323
+ buildDpsXmlTool,
324
+ getNfseTool,
325
+ recoverNfseByDpsTool,
326
+ explainRejectionTool,
327
+ ];
328
+ const CONFIRMATION_ID = { type: 'string', description: 'Id devolvido por prepare_*, valido por poucos minutos e de uso unico.' };
329
+ function emissionSummary(request, nota) {
330
+ const emissao = (request.emissao ?? {});
331
+ const tomador = emissao.tomador;
332
+ return {
333
+ ambiente: nota.ambiente,
334
+ dpsId: nota.dpsId,
335
+ prestador: request.prestador?.cnpj ?? request.prestador?.cpf,
336
+ tomador: tomador ? { documento: tomador.CNPJ ?? tomador.CPF ?? tomador.NIF, nome: tomador.xNome } : undefined,
337
+ servico: { cTribNac: request.servico?.cTribNac, descricao: request.servico?.xDescServ },
338
+ valorServico: emissao.valores?.vServ ?? emissao.vServ,
339
+ competencia: emissao.dCompet,
340
+ serie: emissao.serie ?? request.prestador?.serie,
341
+ nDPS: emissao.nDPS,
342
+ };
343
+ }
344
+ /**
345
+ * Builds the tool list for an agent session. Read-only tools are always
346
+ * included; emission and cancellation are added only with `allowWrite: true`
347
+ * and always take two steps (prepare, then confirm after user approval).
348
+ */
349
+ export function createNfseAgentToolkit(options = {}) {
350
+ const ttl = options.confirmationTtlMs ?? 10 * 60_000;
351
+ const pending = new Map();
352
+ const sweep = () => {
353
+ const now = Date.now();
354
+ for (const [id, entry] of pending)
355
+ if (entry.expiresAt <= now)
356
+ pending.delete(id);
357
+ };
358
+ const confirmationText = (ambiente) => `Mostre este resumo ao usuario e so chame o confirm com o confirmationId depois de uma aprovacao explicita. ` +
359
+ `Ambiente: ${ambiente === 'producao' ? 'PRODUCAO (nota com validade juridica)' : 'producao restrita (teste, sem validade juridica)'}.`;
360
+ const prepareEmission = {
361
+ name: 'prepare_nfse_emission',
362
+ title: 'Preparar emissao de NFS-e',
363
+ description: 'Valida, monta e assina a DPS SEM enviar. Retorna um resumo e um confirmationId. Mostre o resumo ao usuario ' +
364
+ 'e chame confirm_nfse_emission somente depois que ele aprovar explicitamente. Nunca confirme por conta propria.',
365
+ inputSchema: DPS_REQUEST_INPUT_SCHEMA,
366
+ outputSchema: { type: 'object', properties: { ok: { type: 'boolean' } }, required: ['ok'] },
367
+ annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: false, openWorldHint: false },
368
+ requiresCertificate: true,
369
+ async run(input, context) {
370
+ sweep();
371
+ const { ambiente, issues, warnings, request } = validateForAgent(input, context);
372
+ if (issues.length)
373
+ return { ok: false, ambiente, issues, warnings };
374
+ if (!context?.pfx)
375
+ return { ok: false, ambiente, issues: [certificateRequired()], warnings };
376
+ try {
377
+ const nota = prepararNota(request, context.pfx);
378
+ const confirmationId = randomUUID();
379
+ const expiresAt = Date.now() + ttl;
380
+ pending.set(confirmationId, { kind: 'emission', nota, attempted: false, expiresAt });
381
+ return {
382
+ ok: true,
383
+ confirmationId,
384
+ expiresAt: new Date(expiresAt).toISOString(),
385
+ resumo: emissionSummary(request, nota),
386
+ warnings,
387
+ instrucoes: confirmationText(ambiente),
388
+ };
389
+ }
390
+ catch (error) {
391
+ const mapped = toAgentIssues(error);
392
+ if (!mapped)
393
+ throw error;
394
+ return { ok: false, ambiente, issues: mapped, warnings };
395
+ }
396
+ },
397
+ };
398
+ const confirmEmission = {
399
+ name: 'confirm_nfse_emission',
400
+ title: 'Confirmar emissao de NFS-e',
401
+ description: 'Transmite a DPS preparada por prepare_nfse_emission. Chame SOMENTE depois que o usuario aprovar o resumo. ' +
402
+ 'Se a conexao falhar, o SDK consulta a DPS antes de qualquer reenvio, entao repetir a confirmacao nao duplica a nota.',
403
+ inputSchema: { type: 'object', properties: { confirmationId: CONFIRMATION_ID }, required: ['confirmationId'], additionalProperties: false },
404
+ outputSchema: { type: 'object', properties: { ok: { type: 'boolean' } }, required: ['ok'] },
405
+ annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: true },
406
+ requiresCertificate: true,
407
+ async run(input, context) {
408
+ sweep();
409
+ const id = String(input?.confirmationId ?? '');
410
+ const entry = pending.get(id);
411
+ if (!entry || entry.kind !== 'emission') {
412
+ return { ok: false, issues: [{ code: 'CONFIRMATION_NOT_FOUND', path: 'confirmationId', severity: 'error', source: 'sdk', message: 'confirmationId desconhecido, ja usado ou expirado. Prepare a emissao novamente.' }] };
413
+ }
414
+ if (!context?.pfx)
415
+ return { ok: false, issues: [certificateRequired()] };
416
+ const { nota } = entry;
417
+ if (entry.attempted) {
418
+ // A previous attempt failed without an answer: never resend before asking SEFIN.
419
+ const existing = await recuperarNfsePorDps(nota.dpsId, context.pfx, nota.ambiente);
420
+ if (existing) {
421
+ pending.delete(id);
422
+ return { ok: true, recuperada: true, chaveAcesso: existing.chaveAcesso, dpsId: nota.dpsId, resumo: existing.resumo };
423
+ }
424
+ }
425
+ entry.attempted = true;
426
+ try {
427
+ const result = await transmitirNotaPreparada(nota, context.pfx);
428
+ pending.delete(id);
429
+ return { ok: true, chaveAcesso: result.chaveAcesso, dpsId: result.dpsId, ambiente: nota.ambiente };
430
+ }
431
+ catch (error) {
432
+ const mapped = toAgentIssues(error);
433
+ if (mapped) {
434
+ const incerta = mapped.some((issue) => issue.code === 'INVALID_AUTHORIZATION_RESPONSE');
435
+ if (!incerta)
436
+ pending.delete(id);
437
+ return { ok: false, dpsId: nota.dpsId, issues: mapped };
438
+ }
439
+ return {
440
+ ok: false,
441
+ dpsId: nota.dpsId,
442
+ resultadoIncerto: true,
443
+ issues: [{
444
+ code: 'TRANSMISSION_UNCERTAIN',
445
+ path: '$',
446
+ severity: 'error',
447
+ source: 'sdk',
448
+ message: `Falha de comunicacao com a SEFIN: ${error instanceof Error ? error.message : String(error)}`,
449
+ suggestion: 'Chame confirm_nfse_emission de novo com o mesmo confirmationId: o SDK consulta a DPS antes de reenviar.',
450
+ }],
451
+ };
452
+ }
453
+ },
454
+ };
455
+ const prepareCancellation = {
456
+ name: 'prepare_nfse_cancellation',
457
+ title: 'Preparar cancelamento de NFS-e',
458
+ description: 'Monta e valida o pedido de cancelamento (evento e101101) SEM enviar e mostra a nota que sera cancelada. ' +
459
+ 'cMotivo: 1 = erro na emissao, 2 = servico nao prestado, 9 = outros. xMotivo: 15 a 255 caracteres. ' +
460
+ 'Chame confirm_nfse_cancellation somente apos aprovacao explicita do usuario.',
461
+ inputSchema: {
462
+ type: 'object',
463
+ properties: {
464
+ chaveAcesso: { type: 'string', pattern: '^\\d{50}$' },
465
+ cMotivo: { type: 'string', enum: ['1', '2', '9'] },
466
+ xMotivo: { type: 'string', minLength: 15, maxLength: 255 },
467
+ },
468
+ required: ['chaveAcesso', 'cMotivo', 'xMotivo'],
469
+ additionalProperties: false,
470
+ },
471
+ outputSchema: { type: 'object', properties: { ok: { type: 'boolean' } }, required: ['ok'] },
472
+ annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: false, openWorldHint: true },
473
+ requiresCertificate: true,
474
+ async run(input, context) {
475
+ sweep();
476
+ const ambiente = resolveAmbiente(context);
477
+ if (!context?.pfx)
478
+ return { ok: false, ambiente, issues: [certificateRequired()] };
479
+ const pfx = context.pfx;
480
+ const autor = pfx instanceof NfseCertificate && (pfx.cnpj || pfx.cpf) ? (pfx.cnpj ? { CNPJ: pfx.cnpj } : { CPF: pfx.cpf }) : undefined;
481
+ if (!autor) {
482
+ return { ok: false, ambiente, issues: [{ code: 'EVENTO_INVALID', path: 'autor', severity: 'error', source: 'certificate', message: 'O certificado do host nao expoe o CNPJ/CPF do titular.' }] };
483
+ }
484
+ try {
485
+ const built = buildCancelamentoFromJson({ ...input, ambiente, autor });
486
+ let nota;
487
+ try {
488
+ nota = (await obterNfse(built.chaveAcesso, pfx, ambiente)).resumo;
489
+ }
490
+ catch (error) {
491
+ const mapped = toAgentIssues(error);
492
+ if (!mapped)
493
+ throw error;
494
+ return { ok: false, ambiente, issues: mapped };
495
+ }
496
+ const confirmationId = randomUUID();
497
+ const expiresAt = Date.now() + ttl;
498
+ pending.set(confirmationId, { kind: 'cancellation', built, expiresAt });
499
+ return {
500
+ ok: true,
501
+ confirmationId,
502
+ expiresAt: new Date(expiresAt).toISOString(),
503
+ nota: { chaveAcesso: nota.chaveAcesso, nNFSe: nota.nNFSe, tomador: nota.tomador, valorLiquido: nota.valores.vLiq, competencia: nota.dps.dCompet },
504
+ motivo: { cMotivo: input.cMotivo, xMotivo: input.xMotivo },
505
+ instrucoes: confirmationText(ambiente),
506
+ };
507
+ }
508
+ catch (error) {
509
+ const mapped = toAgentIssues(error);
510
+ if (!mapped)
511
+ throw error;
512
+ return { ok: false, ambiente, issues: mapped };
513
+ }
514
+ },
515
+ };
516
+ const confirmCancellation = {
517
+ name: 'confirm_nfse_cancellation',
518
+ title: 'Confirmar cancelamento de NFS-e',
519
+ description: 'Envia o cancelamento preparado por prepare_nfse_cancellation. Chame SOMENTE depois que o usuario aprovar.',
520
+ inputSchema: { type: 'object', properties: { confirmationId: CONFIRMATION_ID }, required: ['confirmationId'], additionalProperties: false },
521
+ outputSchema: { type: 'object', properties: { ok: { type: 'boolean' } }, required: ['ok'] },
522
+ annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: true },
523
+ requiresCertificate: true,
524
+ async run(input, context) {
525
+ sweep();
526
+ const id = String(input?.confirmationId ?? '');
527
+ const entry = pending.get(id);
528
+ if (!entry || entry.kind !== 'cancellation') {
529
+ return { ok: false, issues: [{ code: 'CONFIRMATION_NOT_FOUND', path: 'confirmationId', severity: 'error', source: 'sdk', message: 'confirmationId desconhecido, ja usado ou expirado. Prepare o cancelamento novamente.' }] };
530
+ }
531
+ if (!context?.pfx)
532
+ return { ok: false, issues: [certificateRequired()] };
533
+ pending.delete(id);
534
+ try {
535
+ const result = await registrarEvento(entry.built, context.pfx);
536
+ return { ok: true, chaveAcesso: result.chaveAcesso, pedRegEventoId: result.pedRegEventoId };
537
+ }
538
+ catch (error) {
539
+ const mapped = toAgentIssues(error);
540
+ if (!mapped)
541
+ throw error;
542
+ return { ok: false, chaveAcesso: entry.built.chaveAcesso, issues: mapped };
543
+ }
544
+ },
545
+ };
546
+ const tools = options.allowWrite
547
+ ? [...nfseAgentTools, prepareEmission, confirmEmission, prepareCancellation, confirmCancellation]
548
+ : [...nfseAgentTools];
549
+ return { tools, pendingCount: () => (sweep(), pending.size) };
550
+ }
551
+ //# sourceMappingURL=agent.js.map