agentlang 0.10.9 → 0.11.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 (108) hide show
  1. package/README.md +60 -0
  2. package/out/api/http.d.ts.map +1 -1
  3. package/out/api/http.js +64 -39
  4. package/out/api/http.js.map +1 -1
  5. package/out/cli/main.d.ts +1 -1
  6. package/out/cli/main.d.ts.map +1 -1
  7. package/out/cli/main.js +7 -3
  8. package/out/cli/main.js.map +1 -1
  9. package/out/extension/main.cjs +250 -250
  10. package/out/extension/main.cjs.map +2 -2
  11. package/out/language/main.cjs +504 -504
  12. package/out/language/main.cjs.map +3 -3
  13. package/out/runtime/auth/cognito.d.ts.map +1 -1
  14. package/out/runtime/auth/cognito.js +129 -64
  15. package/out/runtime/auth/cognito.js.map +1 -1
  16. package/out/runtime/defs.d.ts +22 -9
  17. package/out/runtime/defs.d.ts.map +1 -1
  18. package/out/runtime/defs.js +44 -9
  19. package/out/runtime/defs.js.map +1 -1
  20. package/out/runtime/document-retriever.d.ts +24 -0
  21. package/out/runtime/document-retriever.d.ts.map +1 -0
  22. package/out/runtime/document-retriever.js +258 -0
  23. package/out/runtime/document-retriever.js.map +1 -0
  24. package/out/runtime/errors/coded-error.d.ts +8 -0
  25. package/out/runtime/errors/coded-error.d.ts.map +1 -0
  26. package/out/runtime/errors/coded-error.js +13 -0
  27. package/out/runtime/errors/coded-error.js.map +1 -0
  28. package/out/runtime/errors/http-error.d.ts +25 -0
  29. package/out/runtime/errors/http-error.d.ts.map +1 -0
  30. package/out/runtime/errors/http-error.js +169 -0
  31. package/out/runtime/errors/http-error.js.map +1 -0
  32. package/out/runtime/excel-resolver.d.ts +4 -0
  33. package/out/runtime/excel-resolver.d.ts.map +1 -0
  34. package/out/runtime/excel-resolver.js +96 -0
  35. package/out/runtime/excel-resolver.js.map +1 -0
  36. package/out/runtime/excel.d.ts +25 -0
  37. package/out/runtime/excel.d.ts.map +1 -0
  38. package/out/runtime/excel.js +127 -0
  39. package/out/runtime/excel.js.map +1 -0
  40. package/out/runtime/interpreter.d.ts.map +1 -1
  41. package/out/runtime/interpreter.js +26 -25
  42. package/out/runtime/interpreter.js.map +1 -1
  43. package/out/runtime/logger.d.ts +6 -0
  44. package/out/runtime/logger.d.ts.map +1 -1
  45. package/out/runtime/logger.js +21 -0
  46. package/out/runtime/logger.js.map +1 -1
  47. package/out/runtime/module.d.ts.map +1 -1
  48. package/out/runtime/module.js +14 -13
  49. package/out/runtime/module.js.map +1 -1
  50. package/out/runtime/modules/ai.d.ts +1 -0
  51. package/out/runtime/modules/ai.d.ts.map +1 -1
  52. package/out/runtime/modules/ai.js +1 -0
  53. package/out/runtime/modules/ai.js.map +1 -1
  54. package/out/runtime/modules/auth.d.ts.map +1 -1
  55. package/out/runtime/modules/auth.js +35 -12
  56. package/out/runtime/modules/auth.js.map +1 -1
  57. package/out/runtime/modules/core.d.ts +6 -0
  58. package/out/runtime/modules/core.d.ts.map +1 -1
  59. package/out/runtime/modules/core.js +20 -0
  60. package/out/runtime/modules/core.js.map +1 -1
  61. package/out/runtime/resolvers/sqldb/database.d.ts.map +1 -1
  62. package/out/runtime/resolvers/sqldb/database.js +76 -39
  63. package/out/runtime/resolvers/sqldb/database.js.map +1 -1
  64. package/out/runtime/resolvers/sqldb/db-errors.d.ts +6 -0
  65. package/out/runtime/resolvers/sqldb/db-errors.d.ts.map +1 -0
  66. package/out/runtime/resolvers/sqldb/db-errors.js +100 -0
  67. package/out/runtime/resolvers/sqldb/db-errors.js.map +1 -0
  68. package/out/runtime/resolvers/vector/lancedb-store.d.ts +16 -0
  69. package/out/runtime/resolvers/vector/lancedb-store.d.ts.map +1 -0
  70. package/out/runtime/resolvers/vector/lancedb-store.js +159 -0
  71. package/out/runtime/resolvers/vector/lancedb-store.js.map +1 -0
  72. package/out/runtime/resolvers/vector/types.d.ts +32 -0
  73. package/out/runtime/resolvers/vector/types.d.ts.map +1 -0
  74. package/out/runtime/resolvers/vector/types.js +2 -0
  75. package/out/runtime/resolvers/vector/types.js.map +1 -0
  76. package/out/runtime/state.d.ts +3 -0
  77. package/out/runtime/state.d.ts.map +1 -1
  78. package/out/runtime/state.js +7 -0
  79. package/out/runtime/state.js.map +1 -1
  80. package/out/setupClassic.d.ts +98 -0
  81. package/out/setupClassic.d.ts.map +1 -0
  82. package/out/setupClassic.js +38 -0
  83. package/out/setupClassic.js.map +1 -0
  84. package/out/setupCommon.d.ts +2 -0
  85. package/out/setupCommon.d.ts.map +1 -0
  86. package/out/setupCommon.js +33 -0
  87. package/out/setupCommon.js.map +1 -0
  88. package/out/setupExtended.d.ts +40 -0
  89. package/out/setupExtended.d.ts.map +1 -0
  90. package/out/setupExtended.js +67 -0
  91. package/out/setupExtended.js.map +1 -0
  92. package/package.json +19 -18
  93. package/src/api/http.ts +71 -37
  94. package/src/cli/main.ts +12 -4
  95. package/src/runtime/auth/cognito.ts +187 -65
  96. package/src/runtime/defs.ts +51 -18
  97. package/src/runtime/errors/coded-error.ts +18 -0
  98. package/src/runtime/errors/http-error.ts +197 -0
  99. package/src/runtime/interpreter.ts +70 -27
  100. package/src/runtime/logger.ts +27 -0
  101. package/src/runtime/module.ts +45 -13
  102. package/src/runtime/modules/ai.ts +1 -0
  103. package/src/runtime/modules/auth.ts +35 -12
  104. package/src/runtime/modules/core.ts +26 -0
  105. package/src/runtime/resolvers/sqldb/database.ts +88 -37
  106. package/src/runtime/resolvers/sqldb/db-errors.ts +113 -0
  107. package/src/runtime/state.ts +7 -0
  108. package/src/xlsx.d.ts +17 -0
@@ -0,0 +1,18 @@
1
+ /** Stable bucket for errors without an explicit code. */
2
+ export const AL_RUNTIME_UNHANDLED = 'AL_RUNTIME_UNHANDLED';
3
+
4
+ export type CodedError = Error & { agentlangCode: string };
5
+
6
+ export function createCodedError(message: string, code: string): CodedError {
7
+ const e = new Error(message) as CodedError;
8
+ e.agentlangCode = code;
9
+ return e;
10
+ }
11
+
12
+ export function isCodedError(err: unknown): err is CodedError {
13
+ return (
14
+ err instanceof Error &&
15
+ typeof (err as CodedError).agentlangCode === 'string' &&
16
+ (err as CodedError).agentlangCode.length > 0
17
+ );
18
+ }
@@ -0,0 +1,197 @@
1
+ import { AgentCancelledException } from '../modules/ai.js';
2
+ import { lookupCustomErrorMessage } from '../modules/core.js';
3
+ import {
4
+ BadRequestError,
5
+ CodeMismatchError,
6
+ ExpiredCodeError,
7
+ InvalidParameterError,
8
+ PasswordResetRequiredError,
9
+ TooManyRequestsError,
10
+ UnauthorisedError,
11
+ UserNotConfirmedError,
12
+ UserNotFoundError,
13
+ } from '../defs.js';
14
+ import { errorFileLogger, logger } from '../logger.js';
15
+ import { AppConfig } from '../state.js';
16
+ import { AL_RUNTIME_UNHANDLED, isCodedError } from './coded-error.js';
17
+
18
+ /** Registry of Tier-1 HTTP-facing error codes (documentation / consistency). */
19
+ export const AgentlangErrorCodes = {
20
+ AL_RUNTIME_UNHANDLED,
21
+ AL_HTTP_AUTH_REQUIRED: 'AL_HTTP_AUTH_REQUIRED',
22
+ AL_HTTP_HANDLER_EXCEPTION: 'AL_HTTP_HANDLER_EXCEPTION',
23
+ } as const;
24
+
25
+ /** `target` value of an override that applies to any entity for a given code. */
26
+ const UNIVERSAL_TARGET = '*';
27
+
28
+ /** Replace `{{code}}` and `{{message}}` with runtime values (iterates until stable). */
29
+ export function applyErrorMessageTemplate(
30
+ template: string,
31
+ code: string,
32
+ originalMessage: string
33
+ ): string {
34
+ let out = template;
35
+ const maxPasses = 10;
36
+ for (let i = 0; i < maxPasses; i++) {
37
+ const next = out.replace(/\{\{code\}\}/g, code).replace(/\{\{message\}\}/g, originalMessage);
38
+ if (next === out) break;
39
+ out = next;
40
+ }
41
+ return out;
42
+ }
43
+
44
+ function customMessagesEnabled(): boolean {
45
+ return AppConfig?.customErrorMessages?.enabled === true;
46
+ }
47
+
48
+ /**
49
+ * When custom error messages are enabled, append the error to a dedicated
50
+ * `logs/errors-<DATE>.log` file as a single line: timestamp (added by the logger),
51
+ * error code, and error message. No-op when custom messages are disabled.
52
+ */
53
+ export function recordCustomError(code: string, message: string): void {
54
+ if (!customMessagesEnabled()) {
55
+ return;
56
+ }
57
+ try {
58
+ errorFileLogger?.error(`${code} ${message}`);
59
+ } catch (e) {
60
+ logger.warn(`Failed to write error to errors log file: ${e}`);
61
+ }
62
+ }
63
+
64
+ export function getErrorCode(err: unknown): string {
65
+ if (isCodedError(err)) {
66
+ return err.agentlangCode;
67
+ }
68
+ if (err instanceof UnauthorisedError) {
69
+ return err.agentlangCode;
70
+ }
71
+ if (err instanceof BadRequestError) {
72
+ return err.agentlangCode;
73
+ }
74
+ if (err instanceof UserNotFoundError) {
75
+ return err.agentlangCode;
76
+ }
77
+ if (err instanceof UserNotConfirmedError) {
78
+ return err.agentlangCode;
79
+ }
80
+ if (err instanceof PasswordResetRequiredError) {
81
+ return err.agentlangCode;
82
+ }
83
+ if (err instanceof TooManyRequestsError) {
84
+ return err.agentlangCode;
85
+ }
86
+ if (err instanceof InvalidParameterError) {
87
+ return err.agentlangCode;
88
+ }
89
+ if (err instanceof ExpiredCodeError) {
90
+ return err.agentlangCode;
91
+ }
92
+ if (err instanceof CodeMismatchError) {
93
+ return err.agentlangCode;
94
+ }
95
+ if (err instanceof AgentCancelledException) {
96
+ return err.agentlangCode;
97
+ }
98
+ return AL_RUNTIME_UNHANDLED;
99
+ }
100
+
101
+ /**
102
+ * Resolve the message for an entity-route error from the `agentlang/errorMessage`
103
+ * system entity. An entity-specific override (`module/Entry`) wins over a universal
104
+ * (`*`) one for the same code; falls back to `defaultMessage` when neither exists or
105
+ * when custom messages are disabled.
106
+ */
107
+ export async function resolveEntityErrorMessage(
108
+ moduleName: string,
109
+ entryName: string,
110
+ code: string,
111
+ defaultMessage: string
112
+ ): Promise<string> {
113
+ if (!customMessagesEnabled()) {
114
+ return defaultMessage;
115
+ }
116
+ let template: string | undefined;
117
+ try {
118
+ const entityKey = `${moduleName}/${entryName}`;
119
+ template =
120
+ (await lookupCustomErrorMessage(entityKey, code)) ??
121
+ (await lookupCustomErrorMessage(UNIVERSAL_TARGET, code));
122
+ } catch (e) {
123
+ logger.warn(`Failed to resolve custom error message for code ${code}: ${e}`);
124
+ return defaultMessage;
125
+ }
126
+ if (template === undefined) {
127
+ return defaultMessage;
128
+ }
129
+ return applyErrorMessageTemplate(template, code, defaultMessage);
130
+ }
131
+
132
+ export function httpStatusFromError(err: unknown): number {
133
+ const ec = getErrorCode(err);
134
+ if (ec === 'AL_DB_UNIQUE_VIOLATION') {
135
+ return 409;
136
+ }
137
+ if (
138
+ ec === 'AL_DB_FOREIGN_KEY_VIOLATION' ||
139
+ ec === 'AL_DB_NOT_NULL_VIOLATION' ||
140
+ ec === 'AL_DB_CHECK_VIOLATION' ||
141
+ ec === 'AL_DB_INVALID_TYPE' ||
142
+ ec === 'AL_MOD_TYPE_MISMATCH' ||
143
+ ec === 'AL_MOD_INVALID_ATTR'
144
+ ) {
145
+ return 400;
146
+ }
147
+ if (
148
+ ec === 'AL_DB_DEADLOCK' ||
149
+ ec === 'AL_DB_LOCK_WAIT_TIMEOUT' ||
150
+ ec === 'AL_DB_SERIALIZATION_FAILURE'
151
+ ) {
152
+ return 503;
153
+ }
154
+ if (ec === 'AL_DB_SYNTAX_ERROR' || ec === 'AL_DB_QUERY_FAILED') {
155
+ return 500;
156
+ }
157
+ if (err instanceof UserNotFoundError) {
158
+ return 404;
159
+ }
160
+ if (err instanceof UnauthorisedError) {
161
+ return 401;
162
+ }
163
+ if (err instanceof TooManyRequestsError) {
164
+ return 429;
165
+ }
166
+ if (err instanceof BadRequestError || err instanceof InvalidParameterError) {
167
+ return 400;
168
+ }
169
+ if (err instanceof ExpiredCodeError || err instanceof CodeMismatchError) {
170
+ return 400;
171
+ }
172
+ if (err instanceof UserNotConfirmedError || err instanceof PasswordResetRequiredError) {
173
+ return 403;
174
+ }
175
+ if (err instanceof Error && err.message) {
176
+ if (
177
+ err.message.includes('temporarily unavailable') ||
178
+ err.message.includes('service error') ||
179
+ err.message.includes('configuration error')
180
+ ) {
181
+ return 503;
182
+ }
183
+ if (err.message.includes('contact support')) {
184
+ return 500;
185
+ }
186
+ }
187
+ return 500;
188
+ }
189
+
190
+ export function logEntityRouteError(reason: unknown, agentlangCode: string): void {
191
+ if (reason instanceof Error) {
192
+ const stack = reason.stack ? `\n${reason.stack}` : '';
193
+ logger.error(`[${agentlangCode}] ${reason.name}: ${reason.message}${stack}`);
194
+ } else {
195
+ logger.error(`[${agentlangCode}] ${String(reason)}`);
196
+ }
197
+ }
@@ -128,6 +128,7 @@ import { detailedDiff } from 'deep-object-diff';
128
128
  import { callMcpTool, mcpClientNameFromToolEvent } from './mcpclient.js';
129
129
  import { isNodeEnv } from '../utils/runtime.js';
130
130
  import Handlebars from 'handlebars';
131
+ import { createCodedError } from './errors/coded-error.js';
131
132
 
132
133
  export type Result = any;
133
134
 
@@ -446,7 +447,11 @@ export class Environment extends Instance {
446
447
 
447
448
  setActiveEvent(eventInst: Instance | undefined): Environment {
448
449
  if (eventInst) {
449
- if (!isEventInstance(eventInst)) throw new Error(`Not an event instance - ${eventInst.name}`);
450
+ if (!isEventInstance(eventInst))
451
+ throw createCodedError(
452
+ `Not an event instance - ${eventInst.name}`,
453
+ 'AL_INT_NOT_EVENT_INSTANCE'
454
+ );
450
455
  this.bindInstance(eventInst);
451
456
  this.activeModule = eventInst.moduleName;
452
457
  this.activeEventInstance = eventInst;
@@ -532,7 +537,7 @@ export class Environment extends Instance {
532
537
  if (this.suspensionId) {
533
538
  return this.suspensionId;
534
539
  } else {
535
- throw new Error('SuspensionId is not set');
540
+ throw createCodedError('SuspensionId is not set', 'AL_INT_SUSPENSION_ID_NOT_SET');
536
541
  }
537
542
  }
538
543
 
@@ -682,7 +687,7 @@ export class Environment extends Instance {
682
687
  this.activeTransactions.set(n, txnId);
683
688
  return txnId;
684
689
  } else {
685
- throw new Error(`Failed to start transaction for ${n}`);
690
+ throw createCodedError(`Failed to start transaction for ${n}`, 'AL_INT_TXN_START_FAILED');
686
691
  }
687
692
  }
688
693
  }
@@ -774,7 +779,7 @@ export class Environment extends Instance {
774
779
  popHandlers(): CatchHandlers {
775
780
  const r = this.activeCatchHandlers.pop();
776
781
  if (r === undefined) {
777
- throw new Error(`No more handlers to pop`);
782
+ throw createCodedError(`No more handlers to pop`, 'AL_INT_NO_HANDLERS_TO_POP');
778
783
  }
779
784
  return r;
780
785
  }
@@ -1000,7 +1005,7 @@ export let evaluate = async function (
1000
1005
  return null;
1001
1006
  }
1002
1007
  } else {
1003
- throw new Error('Not an event - ' + eventInstance.name);
1008
+ throw createCodedError('Not an event - ' + eventInstance.name, 'AL_INT_NOT_AN_EVENT');
1004
1009
  }
1005
1010
  } catch (err) {
1006
1011
  if (env && env.hasHandlers()) {
@@ -1097,7 +1102,10 @@ async function evaluateAsyncPattern(
1097
1102
  if (s.$cstNode) {
1098
1103
  return s.$cstNode.text;
1099
1104
  } else {
1100
- throw new Error('failed to extract code for suspension statement');
1105
+ throw createCodedError(
1106
+ 'failed to extract code for suspension statement',
1107
+ 'AL_INT_SUSPENSION_CODE_EXTRACT'
1108
+ );
1101
1109
  }
1102
1110
  }),
1103
1111
  env
@@ -1387,7 +1395,7 @@ export async function evaluatePattern(
1387
1395
 
1388
1396
  async function evaluateThrowError(throwErr: ThrowError, env: Environment) {
1389
1397
  await evaluateExpression(throwErr.reason, env);
1390
- throw new Error(env.getLastResult());
1398
+ throw createCodedError(String(env.getLastResult()), 'AL_INT_USER_THROW');
1391
1399
  }
1392
1400
 
1393
1401
  async function evaluateFullTextSearch(fts: FullTextSearch, env: Environment): Promise<void> {
@@ -1397,7 +1405,10 @@ async function evaluateFullTextSearch(fts: FullTextSearch, env: Environment): Pr
1397
1405
  if (inst) {
1398
1406
  n = makeFqName(inst.moduleName, n);
1399
1407
  } else {
1400
- throw new Error(`Fully qualified name required for full-text-search in ${n}`);
1408
+ throw createCodedError(
1409
+ `Fully qualified name required for full-text-search in ${n}`,
1410
+ 'AL_INT_FTS_FQN_REQUIRED'
1411
+ );
1401
1412
  }
1402
1413
  }
1403
1414
  const path = nameToPath(n);
@@ -1407,7 +1418,10 @@ async function evaluateFullTextSearch(fts: FullTextSearch, env: Environment): Pr
1407
1418
  await evaluateLiteral(fts.query, env);
1408
1419
  const q = env.getLastResult();
1409
1420
  if (!isString(q)) {
1410
- throw new Error(`Full text search query must be a string - ${q}`);
1421
+ throw createCodedError(
1422
+ `Full text search query must be a string - ${q}`,
1423
+ 'AL_INT_FTS_QUERY_STRING'
1424
+ );
1411
1425
  }
1412
1426
  let options: Map<string, any> | undefined;
1413
1427
  if (fts.options) {
@@ -1527,7 +1541,10 @@ async function patternToInstance(
1527
1541
  let aname: string = a.name;
1528
1542
  if (aname.endsWith(QuerySuffix)) {
1529
1543
  if (isQueryAll) {
1530
- throw new Error(`Cannot specifiy query attribute ${aname} here`);
1544
+ throw createCodedError(
1545
+ `Cannot specifiy query attribute ${aname} here`,
1546
+ 'AL_INT_QUERY_ATTR_FORBIDDEN'
1547
+ );
1531
1548
  }
1532
1549
  if (qattrs === undefined) qattrs = newInstanceAttributes();
1533
1550
  if (qattrVals === undefined) qattrVals = newInstanceAttributes();
@@ -1570,11 +1587,15 @@ async function instanceFromSource(crud: CrudMap, env: Environment): Promise<Inst
1570
1587
  const m = nparts.hasModule() ? nparts.getModuleName() : env.getActiveModuleName();
1571
1588
  return makeInstance(m, n, attrs);
1572
1589
  } else {
1573
- throw new Error(`Failed to initialize instance of ${crud.name}, expected a map after @from.`);
1590
+ throw createCodedError(
1591
+ `Failed to initialize instance of ${crud.name}, expected a map after @from.`,
1592
+ 'AL_INT_CRUD_INIT_MAP_EXPECTED'
1593
+ );
1574
1594
  }
1575
1595
  } else {
1576
- throw new Error(
1577
- `Cannot create instance of ${crud.name}, CRUD pattern does not specify a source map.`
1596
+ throw createCodedError(
1597
+ `Cannot create instance of ${crud.name}, CRUD pattern does not specify a source map.`,
1598
+ 'AL_INT_CRUD_NO_SOURCE_MAP'
1578
1599
  );
1579
1600
  }
1580
1601
  }
@@ -1589,7 +1610,7 @@ async function maybeValidateOneOfRefs(inst: Instance, env: Environment) {
1589
1610
  const attrSpec = inst.record.schema.get(n);
1590
1611
  if (!attrSpec) continue;
1591
1612
  const r = getOneOfRef(attrSpec);
1592
- if (!r) throw new Error(`Failed to fetch one-of-ref for ${n}`);
1613
+ if (!r) throw createCodedError(`Failed to fetch one-of-ref for ${n}`, 'AL_INT_ONEOF_REF_FETCH');
1593
1614
  if (r) {
1594
1615
  const parts = r.split('.');
1595
1616
  const insts = await lookupOneOfVals(parts[0], env);
@@ -1602,7 +1623,7 @@ async function maybeValidateOneOfRefs(inst: Instance, env: Environment) {
1602
1623
  return i.lookup(parts[1]) == v;
1603
1624
  })
1604
1625
  ) {
1605
- throw new Error(`Invalid enum-value ${v} for ${n}`);
1626
+ throw createCodedError(`Invalid enum-value ${v} for ${n}`, 'AL_INT_INVALID_ENUM');
1606
1627
  }
1607
1628
  }
1608
1629
  }
@@ -1656,12 +1677,16 @@ async function evaluateCrudMap(crud: CrudMap, env: Environment): Promise<void> {
1656
1677
  }
1657
1678
  if (qopts.into) {
1658
1679
  if (attrs.size > 0) {
1659
- throw new Error(
1660
- `Query pattern for ${entryName} with 'into' clause cannot be used to update attributes`
1680
+ throw createCodedError(
1681
+ `Query pattern for ${entryName} with 'into' clause cannot be used to update attributes`,
1682
+ 'AL_INT_INTO_WITH_ATTRS'
1661
1683
  );
1662
1684
  }
1663
1685
  if (qattrs === undefined && !isQueryAll) {
1664
- throw new Error(`Pattern for ${entryName} with 'into' clause must be a query`);
1686
+ throw createCodedError(
1687
+ `Pattern for ${entryName} with 'into' clause must be a query`,
1688
+ 'AL_INT_INTO_MUST_BE_QUERY'
1689
+ );
1665
1690
  }
1666
1691
  if (qopts.joins && qopts.joins.length > 0) {
1667
1692
  await evaluateJoinQuery(qopts.joins, qopts.into, qopts.where, inst, distinct, env);
@@ -1914,7 +1939,10 @@ async function handleDocEvent(inst: Instance, env: Environment): Promise<void> {
1914
1939
  const url = inst.lookup('url');
1915
1940
  if (typeof url === 'string' && url.startsWith('s3://')) {
1916
1941
  if (!isNodeEnv) {
1917
- throw new Error('Document fetching is only available in Node.js environment');
1942
+ throw createCodedError(
1943
+ 'Document fetching is only available in Node.js environment',
1944
+ 'AL_INT_DOC_FETCH_NODE_ONLY'
1945
+ );
1918
1946
  }
1919
1947
  const title = inst.lookup('title');
1920
1948
  const retrievalConfig = inst.lookup('retrievalConfig');
@@ -2213,7 +2241,10 @@ async function walkJoinQueryPattern(
2213
2241
  });
2214
2242
  return joinsSpec;
2215
2243
  } else {
2216
- throw new Error(`Expected a query for relationship ${rp.name}`);
2244
+ throw createCodedError(
2245
+ `Expected a query for relationship ${rp.name}`,
2246
+ 'AL_INT_RELATIONSHIP_QUERY_EXPECTED'
2247
+ );
2217
2248
  }
2218
2249
  }
2219
2250
 
@@ -2338,10 +2369,13 @@ async function agentInvoke(agent: AgentInstance, msg: string, env: Environment):
2338
2369
  }
2339
2370
  }
2340
2371
  } else {
2341
- throw new Error(`Agent ${agent.name} failed to generate a response`);
2372
+ throw createCodedError(
2373
+ `Agent ${agent.name} failed to generate a response`,
2374
+ 'AL_INT_AGENT_NO_RESPONSE'
2375
+ );
2342
2376
  }
2343
2377
  if (agentInternalError !== undefined) {
2344
- throw new Error(agentInternalError);
2378
+ throw createCodedError(agentInternalError, 'AL_INT_AGENT_INTERNAL');
2345
2379
  }
2346
2380
  }
2347
2381
 
@@ -2469,7 +2503,10 @@ async function iterateOnFlow(
2469
2503
  while (step != 'DONE' && !executedSteps.has(step)) {
2470
2504
  await checkCancelled(iterId);
2471
2505
  if (stepc > MaxFlowSteps) {
2472
- throw new Error(`Flow execution exceeded maximum steps limit`);
2506
+ throw createCodedError(
2507
+ `Flow execution exceeded maximum steps limit`,
2508
+ 'AL_INT_FLOW_MAX_STEPS'
2509
+ );
2473
2510
  }
2474
2511
  executedSteps.add(step);
2475
2512
  ++stepc;
@@ -2531,7 +2568,10 @@ async function iterateOnFlow(
2531
2568
  ++fullFlowRetries;
2532
2569
  continue;
2533
2570
  } else {
2534
- throw new Error(reason);
2571
+ throw createCodedError(
2572
+ typeof reason === 'string' ? reason : String(reason),
2573
+ 'AL_INT_FLOW_FATAL'
2574
+ );
2535
2575
  }
2536
2576
  } finally {
2537
2577
  env.decrementMonitor().revokeLastResult().setMonitorFlowResult();
@@ -2739,7 +2779,7 @@ export async function evaluateExpression(expr: Expr, env: Environment): Promise<
2739
2779
  });
2740
2780
  break;
2741
2781
  default:
2742
- throw new Error(`Unrecognized binary operator: ${expr.op}`);
2782
+ throw createCodedError(`Unrecognized binary operator: ${expr.op}`, 'AL_INT_BAD_BINARY_OP');
2743
2783
  }
2744
2784
  } else if (isNegExpr(expr)) {
2745
2785
  await evaluateExpression(expr.ne, env);
@@ -2817,7 +2857,10 @@ async function followReference(env: Environment, s: string): Promise<Result> {
2817
2857
  async function dereferencePath(path: string, env: Environment): Promise<Result> {
2818
2858
  const fqName = fqNameFromPath(path);
2819
2859
  if (fqName === undefined) {
2820
- throw new Error(`Failed to deduce entry-name from path - ${path}`);
2860
+ throw createCodedError(
2861
+ `Failed to deduce entry-name from path - ${path}`,
2862
+ 'AL_INT_DEDUCE_ENTRY_NAME'
2863
+ );
2821
2864
  }
2822
2865
  const newEnv = new Environment('path-deref', env);
2823
2866
  await parseAndEvaluateStatement(
@@ -2925,7 +2968,7 @@ async function runPrePostEvents(
2925
2968
  if (env.hasHandlers()) {
2926
2969
  throw reason;
2927
2970
  } else {
2928
- throw new Error(`${prefix}: ${reason}`);
2971
+ throw createCodedError(`${prefix}: ${reason}`, 'AL_INT_PREPOST_EVENT_FAILED');
2929
2972
  }
2930
2973
  };
2931
2974
  if (trigInfo.async) {
@@ -17,6 +17,13 @@ if (isNodeEnv) {
17
17
 
18
18
  export let logger: any;
19
19
 
20
+ /**
21
+ * Dedicated logger that records each error to `logs/errors-<DATE>.log`, one line
22
+ * per error. Used when custom error messages are enabled (see `recordCustomError`
23
+ * in errors/http-error.ts).
24
+ */
25
+ export let errorFileLogger: any;
26
+
20
27
  function getLogLevel(): string {
21
28
  if (isNodeEnv && process.env && process.env.DEBUG) {
22
29
  return 'debug';
@@ -46,6 +53,25 @@ export function initializeLogger() {
46
53
  ),
47
54
  transports: [fileTransport],
48
55
  });
56
+
57
+ const errorFileTransport = new DailyRotateFile({
58
+ level: 'error',
59
+ filename: 'logs/errors-%DATE%.log',
60
+ datePattern: 'YYYY-MM-DD',
61
+ maxSize: '20m',
62
+ maxFiles: '30d',
63
+ });
64
+
65
+ errorFileLogger = winston.createLogger({
66
+ level: 'error',
67
+ format: winston.format.combine(
68
+ winston.format.timestamp(),
69
+ winston.format.printf(({ timestamp, message }: any) => {
70
+ return `[${timestamp}] ${message}`;
71
+ })
72
+ ),
73
+ transports: [errorFileTransport],
74
+ });
49
75
  } else {
50
76
  function mkLogger(tag: string): Function {
51
77
  return (msg: string) => {
@@ -58,6 +84,7 @@ export function initializeLogger() {
58
84
  warn: mkLogger('WARN'),
59
85
  error: mkLogger('ERROR'),
60
86
  };
87
+ errorFileLogger = { error: mkLogger('ERROR') };
61
88
  }
62
89
  }
63
90
 
@@ -1,4 +1,5 @@
1
1
  import chalk from 'chalk';
2
+ import { createCodedError } from './errors/coded-error.js';
2
3
  import {
3
4
  AttributeDefinition,
4
5
  Expr,
@@ -2631,7 +2632,11 @@ export class Module {
2631
2632
 
2632
2633
  getEntry(entryName: string): ModuleEntry {
2633
2634
  const idx: number = this.getEntryIndex(entryName);
2634
- if (idx < 0) throw new Error(`Entry ${entryName} not found in module ${this.name}`);
2635
+ if (idx < 0)
2636
+ throw createCodedError(
2637
+ `Entry ${entryName} not found in module ${this.name}`,
2638
+ 'AL_MOD_ENTRY_NOT_FOUND'
2639
+ );
2635
2640
  return this.entries[idx];
2636
2641
  }
2637
2642
 
@@ -2646,7 +2651,10 @@ export class Module {
2646
2651
  if (e instanceof Record) {
2647
2652
  return e as Record;
2648
2653
  }
2649
- throw new Error(`${recordName} is not a record in module ${this.name}`);
2654
+ throw createCodedError(
2655
+ `${recordName} is not a record in module ${this.name}`,
2656
+ 'AL_MOD_NOT_A_RECORD'
2657
+ );
2650
2658
  }
2651
2659
 
2652
2660
  removeEntry(entryName: string): boolean {
@@ -2942,7 +2950,7 @@ export function isModule(name: string): boolean {
2942
2950
  export function fetchModule(moduleName: string): Module {
2943
2951
  const module: Module | undefined = getModuleDb().get(moduleName);
2944
2952
  if (module === undefined) {
2945
- throw new Error(`Module not found - ${moduleName}`);
2953
+ throw createCodedError(`Module not found - ${moduleName}`, 'AL_MOD_MODULE_NOT_FOUND');
2946
2954
  }
2947
2955
  return module;
2948
2956
  }
@@ -3481,7 +3489,7 @@ export function getEntity(name: string, moduleName: string): Entity | undefined
3481
3489
  export function fetchEntity(path: Path): Entity {
3482
3490
  const e = getEntity(path.getEntryName(), path.getModuleName());
3483
3491
  if (e === undefined) {
3484
- throw new Error(`Entity not found - ${path.asFqName()}`);
3492
+ throw createCodedError(`Entity not found - ${path.asFqName()}`, 'AL_MOD_ENTITY_NOT_FOUND');
3485
3493
  }
3486
3494
  return e;
3487
3495
  }
@@ -3513,7 +3521,10 @@ export function getEvent(name: string, moduleName: string): Event {
3513
3521
  if (fr.module.isEvent(fr.entryName)) {
3514
3522
  return fr.module.getEntry(fr.entryName) as Event;
3515
3523
  }
3516
- throw new Error(`Event ${fr.entryName} not found in module ${fr.moduleName}`);
3524
+ throw createCodedError(
3525
+ `Event ${fr.entryName} not found in module ${fr.moduleName}`,
3526
+ 'AL_MOD_EVENT_NOT_FOUND'
3527
+ );
3517
3528
  }
3518
3529
 
3519
3530
  export function maybeGetEvent(name: string, moduleName: string): Event | undefined {
@@ -3529,7 +3540,10 @@ export function getRecord(name: string, moduleName: string): Record {
3529
3540
  if (fr.module.isRecord(fr.entryName)) {
3530
3541
  return fr.module.getEntry(fr.entryName) as Record;
3531
3542
  }
3532
- throw new Error(`Record ${fr.entryName} not found in module ${fr.moduleName}`);
3543
+ throw createCodedError(
3544
+ `Record ${fr.entryName} not found in module ${fr.moduleName}`,
3545
+ 'AL_MOD_RECORD_NOT_FOUND'
3546
+ );
3533
3547
  }
3534
3548
 
3535
3549
  export function getRelationship(name: string, moduleName: string): Relationship {
@@ -3537,7 +3551,10 @@ export function getRelationship(name: string, moduleName: string): Relationship
3537
3551
  if (fr.module.isRelationship(fr.entryName)) {
3538
3552
  return fr.module.getEntry(fr.entryName) as Relationship;
3539
3553
  }
3540
- throw new Error(`Relationship ${fr.entryName} not found in module ${fr.moduleName}`);
3554
+ throw createCodedError(
3555
+ `Relationship ${fr.entryName} not found in module ${fr.moduleName}`,
3556
+ 'AL_MOD_RELATIONSHIP_NOT_FOUND'
3557
+ );
3541
3558
  }
3542
3559
 
3543
3560
  export function getAllBetweenRelationships(): Relationship[] {
@@ -3707,7 +3724,10 @@ function checkOneOfValue(attrSpec: AttributeSpec, attrName: string, attrValue: a
3707
3724
  const vals: Set<string> | undefined = getEnumValues(attrSpec);
3708
3725
  if (vals) {
3709
3726
  if (!vals.has(attrValue as string)) {
3710
- throw new Error(`Value of ${attrName} must be one of [${[...vals]}]`);
3727
+ throw createCodedError(
3728
+ `Value of ${attrName} must be one of [${[...vals]}]`,
3729
+ 'AL_MOD_TYPE_MISMATCH'
3730
+ );
3711
3731
  }
3712
3732
  return true;
3713
3733
  }
@@ -3725,7 +3745,10 @@ function getCheckPredicate(attrSpec: AttributeSpec): any {
3725
3745
  function validateType(attrName: string, attrValue: any, attrSpec: AttributeSpec) {
3726
3746
  if (attrSpec.type == 'Path') {
3727
3747
  if (!isPath(attrValue, getRefSpec(attrSpec))) {
3728
- throw new Error(`Failed to validate Path ${attrValue} passed to ${attrName}`);
3748
+ throw createCodedError(
3749
+ `Failed to validate Path ${attrValue} passed to ${attrName}`,
3750
+ 'AL_MOD_TYPE_MISMATCH'
3751
+ );
3729
3752
  }
3730
3753
  }
3731
3754
  let predic = getCheckPredicate(attrSpec);
@@ -3733,16 +3756,22 @@ function validateType(attrName: string, attrValue: any, attrSpec: AttributeSpec)
3733
3756
  if (predic !== undefined) {
3734
3757
  if (isArrayAttribute(attrSpec)) {
3735
3758
  if (!(attrValue instanceof Array)) {
3736
- throw new Error(`${attrName} expects an array of values`);
3759
+ throw createCodedError(`${attrName} expects an array of values`, 'AL_MOD_TYPE_MISMATCH');
3737
3760
  } else {
3738
3761
  if (!attrValue.every(predic)) {
3739
- throw new Error(`Invalid value in the array passed to ${attrName}`);
3762
+ throw createCodedError(
3763
+ `Invalid value in the array passed to ${attrName}`,
3764
+ 'AL_MOD_TYPE_MISMATCH'
3765
+ );
3740
3766
  }
3741
3767
  }
3742
3768
  } else {
3743
3769
  if (!checkOneOfValue(attrSpec, attrName, attrValue)) {
3744
3770
  if (!predic(attrValue)) {
3745
- throw new Error(`Invalid value ${attrValue} specified for ${attrName}`);
3771
+ throw createCodedError(
3772
+ `Invalid value ${attrValue} specified for ${attrName}`,
3773
+ 'AL_MOD_TYPE_MISMATCH'
3774
+ );
3746
3775
  }
3747
3776
  }
3748
3777
  }
@@ -4307,7 +4336,10 @@ export function makeInstance(
4307
4336
  if (schema.size > 0) {
4308
4337
  attributes.forEach((value: any, key: string) => {
4309
4338
  if (!schema.has(key)) {
4310
- throw new Error(`Invalid attribute '${key}' specified for ${moduleName}/${entryName}`);
4339
+ throw createCodedError(
4340
+ `Invalid attribute '${key}' specified for ${moduleName}/${entryName}`,
4341
+ 'AL_MOD_INVALID_ATTR'
4342
+ );
4311
4343
  }
4312
4344
  const spec: AttributeSpec = getAttributeSpec(schema, key);
4313
4345
  if (value !== null && value !== undefined) validateType(key, value, spec);
@@ -85,6 +85,7 @@ const AgentEvalType = 'eval';
85
85
  // --- Agent cancellation infrastructure ---
86
86
 
87
87
  export class AgentCancelledException extends Error {
88
+ readonly agentlangCode = 'AL_AGENT_CANCELLED';
88
89
  constructor(chatId: string) {
89
90
  super(`Agent cancelled for chatId: ${chatId}`);
90
91
  this.name = 'AgentCancelledException';