@databricks/appkit 0.45.0 → 0.46.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 (70) hide show
  1. package/CLAUDE.md +1 -0
  2. package/dist/agents/databricks.d.ts +25 -1
  3. package/dist/agents/databricks.d.ts.map +1 -1
  4. package/dist/agents/databricks.js +20 -1
  5. package/dist/agents/databricks.js.map +1 -1
  6. package/dist/appkit/package.js +1 -1
  7. package/dist/beta.d.ts +2 -2
  8. package/dist/core/agent/load-agents.d.ts.map +1 -1
  9. package/dist/core/agent/load-agents.js +52 -0
  10. package/dist/core/agent/load-agents.js.map +1 -1
  11. package/dist/core/agent/types.d.ts +11 -0
  12. package/dist/core/agent/types.d.ts.map +1 -1
  13. package/dist/core/agent/types.js.map +1 -1
  14. package/dist/core/appkit.d.ts.map +1 -1
  15. package/dist/core/appkit.js +2 -0
  16. package/dist/core/appkit.js.map +1 -1
  17. package/dist/core/lifecycle-manager.js +183 -0
  18. package/dist/core/lifecycle-manager.js.map +1 -0
  19. package/dist/core/plugin-context.d.ts +19 -1
  20. package/dist/core/plugin-context.d.ts.map +1 -1
  21. package/dist/core/plugin-context.js +9 -3
  22. package/dist/core/plugin-context.js.map +1 -1
  23. package/dist/plugin/plugin.d.ts.map +1 -1
  24. package/dist/plugin/plugin.js +2 -1
  25. package/dist/plugin/plugin.js.map +1 -1
  26. package/dist/plugins/agents/agents.d.ts.map +1 -1
  27. package/dist/plugins/agents/agents.js +3 -3
  28. package/dist/plugins/agents/agents.js.map +1 -1
  29. package/dist/plugins/lakebase/lakebase.d.ts +10 -2
  30. package/dist/plugins/lakebase/lakebase.d.ts.map +1 -1
  31. package/dist/plugins/lakebase/lakebase.js +18 -7
  32. package/dist/plugins/lakebase/lakebase.js.map +1 -1
  33. package/dist/plugins/server/index.d.ts +51 -1
  34. package/dist/plugins/server/index.d.ts.map +1 -1
  35. package/dist/plugins/server/index.js +110 -23
  36. package/dist/plugins/server/index.js.map +1 -1
  37. package/dist/registry/resource-registry.d.ts +9 -1
  38. package/dist/registry/resource-registry.d.ts.map +1 -1
  39. package/dist/registry/resource-registry.js +22 -5
  40. package/dist/registry/resource-registry.js.map +1 -1
  41. package/dist/shared/src/execute.d.ts +1 -3
  42. package/dist/shared/src/execute.d.ts.map +1 -1
  43. package/dist/shared/src/plugin.d.ts +16 -0
  44. package/dist/shared/src/plugin.d.ts.map +1 -1
  45. package/dist/stream/buffers.js +1 -1
  46. package/dist/stream/buffers.js.map +1 -1
  47. package/dist/stream/defaults.js +0 -2
  48. package/dist/stream/defaults.js.map +1 -1
  49. package/dist/stream/stream-manager.d.ts +2 -2
  50. package/dist/stream/stream-manager.d.ts.map +1 -1
  51. package/dist/stream/stream-manager.js +26 -24
  52. package/dist/stream/stream-manager.js.map +1 -1
  53. package/dist/stream/stream-registry.js +30 -23
  54. package/dist/stream/stream-registry.js.map +1 -1
  55. package/dist/stream/timers.js +17 -0
  56. package/dist/stream/timers.js.map +1 -0
  57. package/dist/stream/types.js.map +1 -1
  58. package/dist/telemetry/telemetry-manager.js +19 -13
  59. package/dist/telemetry/telemetry-manager.js.map +1 -1
  60. package/dist/utils/safe-handler.js +28 -0
  61. package/dist/utils/safe-handler.js.map +1 -0
  62. package/docs/api/appkit/Class.Plugin.md +2 -0
  63. package/docs/api/appkit/Class.ResourceRegistry.md +1 -1
  64. package/docs/api/appkit/Interface.AgentDefinition.md +11 -0
  65. package/docs/api/appkit/Interface.GenerationParams.md +58 -0
  66. package/docs/api/appkit/Interface.RegisteredAgent.md +11 -0
  67. package/docs/api/appkit.md +1 -0
  68. package/llms.txt +1 -0
  69. package/package.json +1 -1
  70. package/sbom.cdx.json +1 -1
@@ -1,50 +1,56 @@
1
- import { RingBuffer } from "./buffers.js";
2
1
  import { SSEErrorCode } from "./types.js";
2
+ import { clearGraceTimer, clearRemovalTimer } from "./timers.js";
3
3
 
4
4
  //#region src/stream/stream-registry.ts
5
5
  var StreamRegistry = class {
6
6
  streams;
7
+ maxActiveStreams;
7
8
  constructor(maxActiveStreams) {
8
- this.streams = new RingBuffer(maxActiveStreams, (entry) => entry.streamId);
9
+ this.streams = /* @__PURE__ */ new Map();
10
+ this.maxActiveStreams = maxActiveStreams;
9
11
  }
10
12
  add(entry) {
11
- if (this.streams.getSize() >= this.streams.capacity) this._evictOldestStream(entry.streamId);
12
- this.streams.add(entry);
13
+ if (!this.streams.has(entry.streamId) && this.streams.size >= this.maxActiveStreams) this._evictOldestStream(entry.streamId);
14
+ this.streams.set(entry.streamId, entry);
13
15
  }
14
16
  get(streamId) {
15
- return this.streams.get(streamId);
17
+ return this.streams.get(streamId) ?? null;
16
18
  }
17
19
  has(streamId) {
18
20
  return this.streams.has(streamId);
19
21
  }
20
22
  remove(streamId) {
21
- this.streams.remove(streamId);
23
+ this.streams.delete(streamId);
22
24
  }
23
25
  size() {
24
- return this.streams.getSize();
26
+ return this.streams.size;
25
27
  }
26
28
  clear() {
27
- const allStreams = this.streams.getAll();
28
- for (const stream of allStreams) {
29
- this._clearGraceTimer(stream);
29
+ for (const stream of this.streams.values()) {
30
30
  stream.abortController.abort("Server shutdown");
31
+ clearGraceTimer(stream);
32
+ clearRemovalTimer(stream);
31
33
  }
32
34
  this.streams.clear();
33
35
  }
34
- _clearGraceTimer(stream) {
35
- if (stream.disconnectGraceTimer) {
36
- clearTimeout(stream.disconnectGraceTimer);
37
- stream.disconnectGraceTimer = void 0;
38
- }
39
- }
40
36
  _evictOldestStream(excludeStreamId) {
41
- const allStreams = this.streams.getAll();
37
+ const allStreams = this.streams.values();
42
38
  let oldestStream = null;
43
39
  let oldestAccess = Infinity;
44
- for (const stream of allStreams) if (stream.streamId !== excludeStreamId && stream.lastAccess < oldestAccess) {
45
- oldestStream = stream;
46
- oldestAccess = stream.lastAccess;
40
+ let oldestCompletedStream = null;
41
+ let oldestCompletedAccess = Infinity;
42
+ for (const stream of allStreams) {
43
+ if (stream.streamId === excludeStreamId) continue;
44
+ if (stream.lastAccess < oldestAccess) {
45
+ oldestStream = stream;
46
+ oldestAccess = stream.lastAccess;
47
+ }
48
+ if (stream.isCompleted && stream.lastAccess < oldestCompletedAccess) {
49
+ oldestCompletedStream = stream;
50
+ oldestCompletedAccess = stream.lastAccess;
51
+ }
47
52
  }
53
+ oldestStream = oldestCompletedStream ?? oldestStream;
48
54
  if (oldestStream) {
49
55
  for (const client of oldestStream.clients) if (!client.writableEnded) try {
50
56
  client.write(`event: error\n`);
@@ -53,9 +59,10 @@ var StreamRegistry = class {
53
59
  code: SSEErrorCode.STREAM_EVICTED
54
60
  })}\n\n`);
55
61
  } catch (_error) {}
56
- this._clearGraceTimer(oldestStream);
57
- oldestStream.abortController.abort("Stream evicted");
58
- this.streams.remove(oldestStream.streamId);
62
+ clearGraceTimer(oldestStream);
63
+ oldestStream.abortController.abort(new DOMException("Stream evicted", "AbortError"));
64
+ clearRemovalTimer(oldestStream);
65
+ this.streams.delete(oldestStream.streamId);
59
66
  }
60
67
  }
61
68
  };
@@ -1 +1 @@
1
- {"version":3,"file":"stream-registry.js","names":[],"sources":["../../src/stream/stream-registry.ts"],"sourcesContent":["import { RingBuffer } from \"./buffers\";\nimport { SSEErrorCode, type StreamEntry } from \"./types\";\n\nexport class StreamRegistry {\n private streams: RingBuffer<StreamEntry>;\n\n constructor(maxActiveStreams: number) {\n this.streams = new RingBuffer<StreamEntry>(\n maxActiveStreams,\n (entry) => entry.streamId,\n );\n }\n\n // add a stream to the registry\n add(entry: StreamEntry): void {\n // enforce hard cap\n if (this.streams.getSize() >= this.streams.capacity) {\n this._evictOldestStream(entry.streamId);\n }\n\n this.streams.add(entry);\n }\n\n // get a stream from the registry\n get(streamId: string): StreamEntry | null {\n return this.streams.get(streamId);\n }\n\n // check if a stream exists in the registry\n has(streamId: string): boolean {\n return this.streams.has(streamId);\n }\n\n // remove a stream from the registry\n remove(streamId: string): void {\n this.streams.remove(streamId);\n }\n\n // get the number of streams in the registry\n size(): number {\n return this.streams.getSize();\n }\n\n clear(): void {\n const allStreams = this.streams.getAll();\n\n for (const stream of allStreams) {\n this._clearGraceTimer(stream);\n stream.abortController.abort(\"Server shutdown\");\n }\n\n this.streams.clear();\n }\n\n // clear a pending grace timer so a removed stream isn't pinned until it fires\n private _clearGraceTimer(stream: StreamEntry): void {\n if (stream.disconnectGraceTimer) {\n clearTimeout(stream.disconnectGraceTimer);\n stream.disconnectGraceTimer = undefined;\n }\n }\n\n // evict the oldest stream from the registry\n private _evictOldestStream(excludeStreamId: string): void {\n const allStreams = this.streams.getAll();\n let oldestStream: StreamEntry | null = null;\n let oldestAccess = Infinity;\n\n // find the least recently accessed stream\n for (const stream of allStreams) {\n if (\n stream.streamId !== excludeStreamId &&\n stream.lastAccess < oldestAccess\n ) {\n oldestStream = stream;\n oldestAccess = stream.lastAccess;\n }\n }\n\n // abort the oldest stream\n if (oldestStream) {\n // broadcast stream eviction error to all clients\n for (const client of oldestStream.clients) {\n if (!client.writableEnded) {\n try {\n client.write(`event: error\\n`);\n client.write(\n `data: ${JSON.stringify({ error: \"Stream evicted\", code: SSEErrorCode.STREAM_EVICTED })}\\n\\n`,\n );\n } catch (_error) {\n // ignore\n }\n }\n }\n this._clearGraceTimer(oldestStream);\n oldestStream.abortController.abort(\"Stream evicted\");\n this.streams.remove(oldestStream.streamId);\n }\n }\n}\n"],"mappings":";;;;AAGA,IAAa,iBAAb,MAA4B;CAC1B,AAAQ;CAER,YAAY,kBAA0B;AACpC,OAAK,UAAU,IAAI,WACjB,mBACC,UAAU,MAAM,SAClB;;CAIH,IAAI,OAA0B;AAE5B,MAAI,KAAK,QAAQ,SAAS,IAAI,KAAK,QAAQ,SACzC,MAAK,mBAAmB,MAAM,SAAS;AAGzC,OAAK,QAAQ,IAAI,MAAM;;CAIzB,IAAI,UAAsC;AACxC,SAAO,KAAK,QAAQ,IAAI,SAAS;;CAInC,IAAI,UAA2B;AAC7B,SAAO,KAAK,QAAQ,IAAI,SAAS;;CAInC,OAAO,UAAwB;AAC7B,OAAK,QAAQ,OAAO,SAAS;;CAI/B,OAAe;AACb,SAAO,KAAK,QAAQ,SAAS;;CAG/B,QAAc;EACZ,MAAM,aAAa,KAAK,QAAQ,QAAQ;AAExC,OAAK,MAAM,UAAU,YAAY;AAC/B,QAAK,iBAAiB,OAAO;AAC7B,UAAO,gBAAgB,MAAM,kBAAkB;;AAGjD,OAAK,QAAQ,OAAO;;CAItB,AAAQ,iBAAiB,QAA2B;AAClD,MAAI,OAAO,sBAAsB;AAC/B,gBAAa,OAAO,qBAAqB;AACzC,UAAO,uBAAuB;;;CAKlC,AAAQ,mBAAmB,iBAA+B;EACxD,MAAM,aAAa,KAAK,QAAQ,QAAQ;EACxC,IAAI,eAAmC;EACvC,IAAI,eAAe;AAGnB,OAAK,MAAM,UAAU,WACnB,KACE,OAAO,aAAa,mBACpB,OAAO,aAAa,cACpB;AACA,kBAAe;AACf,kBAAe,OAAO;;AAK1B,MAAI,cAAc;AAEhB,QAAK,MAAM,UAAU,aAAa,QAChC,KAAI,CAAC,OAAO,cACV,KAAI;AACF,WAAO,MAAM,iBAAiB;AAC9B,WAAO,MACL,SAAS,KAAK,UAAU;KAAE,OAAO;KAAkB,MAAM,aAAa;KAAgB,CAAC,CAAC,MACzF;YACM,QAAQ;AAKrB,QAAK,iBAAiB,aAAa;AACnC,gBAAa,gBAAgB,MAAM,iBAAiB;AACpD,QAAK,QAAQ,OAAO,aAAa,SAAS"}
1
+ {"version":3,"file":"stream-registry.js","names":[],"sources":["../../src/stream/stream-registry.ts"],"sourcesContent":["import { clearGraceTimer, clearRemovalTimer } from \"./timers\";\nimport { SSEErrorCode, type StreamEntry } from \"./types\";\n\nexport class StreamRegistry {\n // keyed storage with explicit, policy-driven eviction. A ring buffer is\n // unsuitable here: it overwrites by insertion slot, so evicting an entry\n // chosen by policy (e.g. a completed stream) and then adding would\n // silently clobber an unrelated live stream sitting in the oldest slot.\n private streams: Map<string, StreamEntry>;\n private maxActiveStreams: number;\n\n constructor(maxActiveStreams: number) {\n this.streams = new Map();\n this.maxActiveStreams = maxActiveStreams;\n }\n\n // add a stream to the registry\n add(entry: StreamEntry): void {\n // enforce hard cap, but only when inserting a genuinely new key:\n // re-adding an existing streamId updates in place and doesn't grow the\n // map, so evicting another stream for it would destroy an innocent one\n if (\n !this.streams.has(entry.streamId) &&\n this.streams.size >= this.maxActiveStreams\n ) {\n this._evictOldestStream(entry.streamId);\n }\n\n this.streams.set(entry.streamId, entry);\n }\n\n // get a stream from the registry\n get(streamId: string): StreamEntry | null {\n return this.streams.get(streamId) ?? null;\n }\n\n // check if a stream exists in the registry\n has(streamId: string): boolean {\n return this.streams.has(streamId);\n }\n\n // remove a stream from the registry\n remove(streamId: string): void {\n this.streams.delete(streamId);\n }\n\n // get the number of streams in the registry\n size(): number {\n return this.streams.size;\n }\n\n clear(): void {\n for (const stream of this.streams.values()) {\n stream.abortController.abort(\"Server shutdown\");\n clearGraceTimer(stream);\n clearRemovalTimer(stream);\n }\n\n this.streams.clear();\n }\n\n // evict the oldest stream from the registry, preferring completed streams.\n // Completed streams waiting out their buffer TTL can look recently\n // accessed, so plain LRU could evict a live stream while dead ones\n // survive. Prefer the oldest completed stream when one exists and fall\n // back to LRU over all streams otherwise.\n private _evictOldestStream(excludeStreamId: string): void {\n const allStreams = this.streams.values();\n let oldestStream: StreamEntry | null = null;\n let oldestAccess = Infinity;\n let oldestCompletedStream: StreamEntry | null = null;\n let oldestCompletedAccess = Infinity;\n\n // find the least recently accessed stream (overall and completed-only)\n for (const stream of allStreams) {\n if (stream.streamId === excludeStreamId) continue;\n\n if (stream.lastAccess < oldestAccess) {\n oldestStream = stream;\n oldestAccess = stream.lastAccess;\n }\n\n if (stream.isCompleted && stream.lastAccess < oldestCompletedAccess) {\n oldestCompletedStream = stream;\n oldestCompletedAccess = stream.lastAccess;\n }\n }\n\n oldestStream = oldestCompletedStream ?? oldestStream;\n\n // abort the evicted stream\n if (oldestStream) {\n // broadcast stream eviction error to all clients\n for (const client of oldestStream.clients) {\n if (!client.writableEnded) {\n try {\n client.write(`event: error\\n`);\n client.write(\n `data: ${JSON.stringify({ error: \"Stream evicted\", code: SSEErrorCode.STREAM_EVICTED })}\\n\\n`,\n );\n } catch (_error) {\n // ignore\n }\n }\n }\n clearGraceTimer(oldestStream);\n // abort with a DOMException AbortError so the error categorizer routes\n // eviction as an abort (matching the manager's terminal paths) rather\n // than a stream failure\n oldestStream.abortController.abort(\n new DOMException(\"Stream evicted\", \"AbortError\"),\n );\n // a pending removal timer would otherwise pin the evicted entry\n clearRemovalTimer(oldestStream);\n this.streams.delete(oldestStream.streamId);\n }\n }\n}\n"],"mappings":";;;;AAGA,IAAa,iBAAb,MAA4B;CAK1B,AAAQ;CACR,AAAQ;CAER,YAAY,kBAA0B;AACpC,OAAK,0BAAU,IAAI,KAAK;AACxB,OAAK,mBAAmB;;CAI1B,IAAI,OAA0B;AAI5B,MACE,CAAC,KAAK,QAAQ,IAAI,MAAM,SAAS,IACjC,KAAK,QAAQ,QAAQ,KAAK,iBAE1B,MAAK,mBAAmB,MAAM,SAAS;AAGzC,OAAK,QAAQ,IAAI,MAAM,UAAU,MAAM;;CAIzC,IAAI,UAAsC;AACxC,SAAO,KAAK,QAAQ,IAAI,SAAS,IAAI;;CAIvC,IAAI,UAA2B;AAC7B,SAAO,KAAK,QAAQ,IAAI,SAAS;;CAInC,OAAO,UAAwB;AAC7B,OAAK,QAAQ,OAAO,SAAS;;CAI/B,OAAe;AACb,SAAO,KAAK,QAAQ;;CAGtB,QAAc;AACZ,OAAK,MAAM,UAAU,KAAK,QAAQ,QAAQ,EAAE;AAC1C,UAAO,gBAAgB,MAAM,kBAAkB;AAC/C,mBAAgB,OAAO;AACvB,qBAAkB,OAAO;;AAG3B,OAAK,QAAQ,OAAO;;CAQtB,AAAQ,mBAAmB,iBAA+B;EACxD,MAAM,aAAa,KAAK,QAAQ,QAAQ;EACxC,IAAI,eAAmC;EACvC,IAAI,eAAe;EACnB,IAAI,wBAA4C;EAChD,IAAI,wBAAwB;AAG5B,OAAK,MAAM,UAAU,YAAY;AAC/B,OAAI,OAAO,aAAa,gBAAiB;AAEzC,OAAI,OAAO,aAAa,cAAc;AACpC,mBAAe;AACf,mBAAe,OAAO;;AAGxB,OAAI,OAAO,eAAe,OAAO,aAAa,uBAAuB;AACnE,4BAAwB;AACxB,4BAAwB,OAAO;;;AAInC,iBAAe,yBAAyB;AAGxC,MAAI,cAAc;AAEhB,QAAK,MAAM,UAAU,aAAa,QAChC,KAAI,CAAC,OAAO,cACV,KAAI;AACF,WAAO,MAAM,iBAAiB;AAC9B,WAAO,MACL,SAAS,KAAK,UAAU;KAAE,OAAO;KAAkB,MAAM,aAAa;KAAgB,CAAC,CAAC,MACzF;YACM,QAAQ;AAKrB,mBAAgB,aAAa;AAI7B,gBAAa,gBAAgB,MAC3B,IAAI,aAAa,kBAAkB,aAAa,CACjD;AAED,qBAAkB,aAAa;AAC/B,QAAK,QAAQ,OAAO,aAAa,SAAS"}
@@ -0,0 +1,17 @@
1
+ //#region src/stream/timers.ts
2
+ function clearGraceTimer(entry) {
3
+ if (entry.disconnectGraceTimer) {
4
+ clearTimeout(entry.disconnectGraceTimer);
5
+ entry.disconnectGraceTimer = void 0;
6
+ }
7
+ }
8
+ function clearRemovalTimer(entry) {
9
+ if (entry.removalTimer) {
10
+ clearTimeout(entry.removalTimer);
11
+ entry.removalTimer = void 0;
12
+ }
13
+ }
14
+
15
+ //#endregion
16
+ export { clearGraceTimer, clearRemovalTimer };
17
+ //# sourceMappingURL=timers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"timers.js","names":[],"sources":["../../src/stream/timers.ts"],"sourcesContent":["import type { StreamEntry } from \"./types\";\n\n// clear a pending disconnect-grace timer so a removed/reconnected stream\n// isn't pinned until it fires\nexport function clearGraceTimer(entry: StreamEntry): void {\n if (entry.disconnectGraceTimer) {\n clearTimeout(entry.disconnectGraceTimer);\n entry.disconnectGraceTimer = undefined;\n }\n}\n\n// clear a pending registry-removal timer so a reconnected/evicted stream\n// isn't pulled out from under a client\nexport function clearRemovalTimer(entry: StreamEntry): void {\n if (entry.removalTimer) {\n clearTimeout(entry.removalTimer);\n entry.removalTimer = undefined;\n }\n}\n"],"mappings":";AAIA,SAAgB,gBAAgB,OAA0B;AACxD,KAAI,MAAM,sBAAsB;AAC9B,eAAa,MAAM,qBAAqB;AACxC,QAAM,uBAAuB;;;AAMjC,SAAgB,kBAAkB,OAA0B;AAC1D,KAAI,MAAM,cAAc;AACtB,eAAa,MAAM,aAAa;AAChC,QAAM,eAAe"}
@@ -1 +1 @@
1
- {"version":3,"file":"types.js","names":[],"sources":["../../src/stream/types.ts"],"sourcesContent":["import type { Context } from \"@opentelemetry/api\";\nimport type { IAppResponse } from \"shared\";\nimport type { EventRingBuffer } from \"./buffers\";\n\nexport const SSEWarningCode = {\n BUFFER_OVERFLOW_RESTART: \"BUFFER_OVERFLOW_RESTART\",\n} as const satisfies Record<string, string>;\n\nexport type SSEWarningCode =\n (typeof SSEWarningCode)[keyof typeof SSEWarningCode];\n\nexport const SSEErrorCode = {\n TEMPORARY_UNAVAILABLE: \"TEMPORARY_UNAVAILABLE\",\n TIMEOUT: \"TIMEOUT\",\n INTERNAL_ERROR: \"INTERNAL_ERROR\",\n INVALID_REQUEST: \"INVALID_REQUEST\",\n STREAM_ABORTED: \"STREAM_ABORTED\",\n STREAM_EVICTED: \"STREAM_EVICTED\",\n STREAM_FORBIDDEN: \"STREAM_FORBIDDEN\",\n UPSTREAM_ERROR: \"UPSTREAM_ERROR\",\n} as const satisfies Record<string, string>;\n\nexport type SSEErrorCode = (typeof SSEErrorCode)[keyof typeof SSEErrorCode];\n\nexport interface SSEError {\n error: string;\n code: SSEErrorCode;\n /**\n * Upstream-domain structured code (e.g. `RESULT_TOO_LARGE_FOR_JSON_FALLBACK`,\n * `NOT_IMPLEMENTED`). UI code should branch on this instead of parsing\n * the human-readable `error` string.\n */\n errorCode?: string;\n}\n\nexport interface BufferedEvent {\n id: string;\n type: string;\n data: string;\n timestamp: number;\n}\n\nexport interface StreamEntry {\n streamId: string;\n /**\n * Identifier of the principal that created the stream (e.g. end-user ID\n * or service principal user ID). When set, only requests sharing the\n * same owner key may reconnect to the stream.\n */\n ownerKey?: string;\n generator: AsyncGenerator<any, void, unknown>;\n eventBuffer: EventRingBuffer;\n clients: Set<IAppResponse>;\n isCompleted: boolean;\n lastAccess: number;\n abortController: AbortController;\n traceContext: Context;\n // pending grace-window abort, set while the last client is disconnected\n disconnectGraceTimer?: NodeJS.Timeout;\n}\n\nexport interface StreamOperation {\n controller: AbortController;\n type: \"query\" | \"stream\";\n heartbeat?: NodeJS.Timeout;\n}\n"],"mappings":";AAIA,MAAa,iBAAiB,EAC5B,yBAAyB,2BAC1B;AAKD,MAAa,eAAe;CAC1B,uBAAuB;CACvB,SAAS;CACT,gBAAgB;CAChB,iBAAiB;CACjB,gBAAgB;CAChB,gBAAgB;CAChB,kBAAkB;CAClB,gBAAgB;CACjB"}
1
+ {"version":3,"file":"types.js","names":[],"sources":["../../src/stream/types.ts"],"sourcesContent":["import type { Context } from \"@opentelemetry/api\";\nimport type { IAppResponse } from \"shared\";\nimport type { EventRingBuffer } from \"./buffers\";\n\nexport const SSEWarningCode = {\n BUFFER_OVERFLOW_RESTART: \"BUFFER_OVERFLOW_RESTART\",\n} as const satisfies Record<string, string>;\n\nexport type SSEWarningCode =\n (typeof SSEWarningCode)[keyof typeof SSEWarningCode];\n\nexport const SSEErrorCode = {\n TEMPORARY_UNAVAILABLE: \"TEMPORARY_UNAVAILABLE\",\n TIMEOUT: \"TIMEOUT\",\n INTERNAL_ERROR: \"INTERNAL_ERROR\",\n INVALID_REQUEST: \"INVALID_REQUEST\",\n STREAM_ABORTED: \"STREAM_ABORTED\",\n STREAM_EVICTED: \"STREAM_EVICTED\",\n STREAM_FORBIDDEN: \"STREAM_FORBIDDEN\",\n UPSTREAM_ERROR: \"UPSTREAM_ERROR\",\n} as const satisfies Record<string, string>;\n\nexport type SSEErrorCode = (typeof SSEErrorCode)[keyof typeof SSEErrorCode];\n\nexport interface SSEError {\n error: string;\n code: SSEErrorCode;\n /**\n * Upstream-domain structured code (e.g. `RESULT_TOO_LARGE_FOR_JSON_FALLBACK`,\n * `NOT_IMPLEMENTED`). UI code should branch on this instead of parsing\n * the human-readable `error` string.\n */\n errorCode?: string;\n}\n\nexport interface BufferedEvent {\n id: string;\n type: string;\n data: string;\n timestamp: number;\n}\n\nexport interface StreamEntry {\n streamId: string;\n /**\n * Identifier of the principal that created the stream (e.g. end-user ID\n * or service principal user ID). When set, only requests sharing the\n * same owner key may reconnect to the stream.\n */\n ownerKey?: string;\n generator: AsyncGenerator<any, void, unknown>;\n eventBuffer: EventRingBuffer;\n clients: Set<IAppResponse>;\n isCompleted: boolean;\n lastAccess: number;\n abortController: AbortController;\n traceContext: Context;\n // pending grace-window abort, set while the last client is disconnected\n disconnectGraceTimer?: NodeJS.Timeout;\n /**\n * Pending registry-removal timer. At most one removal timer exists per\n * stream; scheduling a new one clears any previous timer first.\n */\n removalTimer?: NodeJS.Timeout;\n}\n\nexport interface StreamOperation {\n controller: AbortController;\n type: \"query\" | \"stream\";\n heartbeat?: NodeJS.Timeout;\n}\n"],"mappings":";AAIA,MAAa,iBAAiB,EAC5B,yBAAyB,2BAC1B;AAKD,MAAa,eAAe;CAC1B,uBAAuB;CACvB,SAAS;CACT,gBAAgB;CAChB,iBAAiB;CACjB,gBAAgB;CAChB,gBAAgB;CAChB,kBAAkB;CAClB,gBAAgB;CACjB"}
@@ -19,6 +19,7 @@ var TelemetryManager = class TelemetryManager {
19
19
  static DEFAULT_FALLBACK_APP_NAME = "databricks-app";
20
20
  static instance;
21
21
  sdk;
22
+ shutdownPromise;
22
23
  /**
23
24
  * Create a scoped telemetry provider for a specific plugin.
24
25
  * The plugin's name will be used as the default tracer/meter name.
@@ -54,7 +55,6 @@ var TelemetryManager = class TelemetryManager {
54
55
  instrumentations: this.getDefaultInstrumentations()
55
56
  });
56
57
  this.sdk.start();
57
- this.registerShutdown();
58
58
  logger.debug("Initialized successfully");
59
59
  } catch (error) {
60
60
  logger.error("Failed to initialize: %O", error);
@@ -90,21 +90,27 @@ var TelemetryManager = class TelemetryManager {
90
90
  "@opentelemetry/instrumentation-net": { enabled: false }
91
91
  })];
92
92
  }
93
- registerShutdown() {
94
- const shutdownFn = async () => {
95
- await TelemetryManager.getInstance().shutdown();
96
- };
97
- process.once("SIGTERM", shutdownFn);
98
- process.once("SIGINT", shutdownFn);
99
- }
93
+ /**
94
+ * Flush and shut down the OpenTelemetry SDK.
95
+ *
96
+ * Idempotent: the SDK reference is cleared synchronously and concurrent
97
+ * or repeated calls await the same in-flight flush. Awaited by the core
98
+ * lifecycle manager during graceful shutdown — that manager owns the
99
+ * process signal handlers, so telemetry no longer registers its own.
100
+ */
100
101
  async shutdown() {
101
- if (!this.sdk) return;
102
- try {
103
- await this.sdk.shutdown();
102
+ if (this.sdk) {
103
+ const sdk = this.sdk;
104
104
  this.sdk = void 0;
105
- } catch (error) {
106
- logger.error("Error shutting down: %O", error);
105
+ this.shutdownPromise = (async () => {
106
+ try {
107
+ await sdk.shutdown();
108
+ } catch (error) {
109
+ logger.error("Error shutting down: %O", error);
110
+ }
111
+ })();
107
112
  }
113
+ return this.shutdownPromise;
108
114
  }
109
115
  };
110
116
 
@@ -1 +1 @@
1
- {"version":3,"file":"telemetry-manager.js","names":[],"sources":["../../src/telemetry/telemetry-manager.ts"],"sourcesContent":["import { getNodeAutoInstrumentations } from \"@opentelemetry/auto-instrumentations-node\";\nimport { OTLPLogExporter } from \"@opentelemetry/exporter-logs-otlp-proto\";\nimport { OTLPMetricExporter } from \"@opentelemetry/exporter-metrics-otlp-proto\";\nimport { OTLPTraceExporter } from \"@opentelemetry/exporter-trace-otlp-proto\";\nimport {\n type Instrumentation,\n registerInstrumentations as otelRegisterInstrumentations,\n} from \"@opentelemetry/instrumentation\";\nimport {\n detectResources,\n envDetector,\n hostDetector,\n processDetector,\n type Resource,\n resourceFromAttributes,\n} from \"@opentelemetry/resources\";\nimport { BatchLogRecordProcessor } from \"@opentelemetry/sdk-logs\";\nimport { PeriodicExportingMetricReader } from \"@opentelemetry/sdk-metrics\";\nimport { NodeSDK } from \"@opentelemetry/sdk-node\";\nimport {\n ATTR_SERVICE_NAME,\n ATTR_SERVICE_VERSION,\n} from \"@opentelemetry/semantic-conventions\";\nimport type { TelemetryOptions } from \"shared\";\nimport { createLogger } from \"../logging/logger\";\nimport { TelemetryProvider } from \"./telemetry-provider\";\nimport { AppKitSampler } from \"./trace-sampler\";\nimport type { TelemetryConfig } from \"./types\";\n\nconst logger = createLogger(\"telemetry\");\n\nexport class TelemetryManager {\n private static readonly DEFAULT_EXPORT_INTERVAL_MS = 10000;\n private static readonly DEFAULT_FALLBACK_APP_NAME = \"databricks-app\";\n\n private static instance?: TelemetryManager;\n private sdk?: NodeSDK;\n\n /**\n * Create a scoped telemetry provider for a specific plugin.\n * The plugin's name will be used as the default tracer/meter name.\n * @param pluginName - The name of the plugin to create scoped telemetry for\n * @param telemetryConfig - The telemetry configuration for the plugin\n * @returns A scoped telemetry instance for the plugin\n */\n static getProvider(\n pluginName: string,\n telemetryConfig?: TelemetryOptions,\n ): TelemetryProvider {\n const globalManager = TelemetryManager.getInstance();\n return new TelemetryProvider(pluginName, globalManager, telemetryConfig);\n }\n\n private constructor() {}\n\n static getInstance(): TelemetryManager {\n if (!TelemetryManager.instance) {\n TelemetryManager.instance = new TelemetryManager();\n }\n return TelemetryManager.instance;\n }\n\n static initialize(config: Partial<TelemetryConfig> = {}): void {\n const instance = TelemetryManager.getInstance();\n instance._initialize(config);\n }\n\n private _initialize(config: Partial<TelemetryConfig>): void {\n if (this.sdk) return;\n\n if (!process.env.OTEL_EXPORTER_OTLP_ENDPOINT) {\n return;\n }\n\n try {\n this.sdk = new NodeSDK({\n resource: this.createResource(config),\n autoDetectResources: false,\n sampler: new AppKitSampler(),\n traceExporter: new OTLPTraceExporter({ headers: config.headers }),\n metricReaders: [\n new PeriodicExportingMetricReader({\n exporter: new OTLPMetricExporter({ headers: config.headers }),\n exportIntervalMillis:\n config.exportIntervalMs ||\n TelemetryManager.DEFAULT_EXPORT_INTERVAL_MS,\n }),\n ],\n logRecordProcessors: [\n new BatchLogRecordProcessor(\n new OTLPLogExporter({ headers: config.headers }),\n ),\n ],\n instrumentations: this.getDefaultInstrumentations(),\n });\n\n this.sdk.start();\n this.registerShutdown();\n logger.debug(\"Initialized successfully\");\n } catch (error) {\n logger.error(\"Failed to initialize: %O\", error);\n }\n }\n\n /**\n * Register OpenTelemetry instrumentations.\n * Can be called at any time, but recommended to call in plugin constructor.\n * @param instrumentations - Array of OpenTelemetry instrumentations to register\n */\n registerInstrumentations(instrumentations: Instrumentation[]): void {\n otelRegisterInstrumentations({\n // global providers set by NodeSDK.start()\n instrumentations,\n });\n }\n\n private createResource(config: Partial<TelemetryConfig>): Resource {\n const serviceName =\n config.serviceName ||\n process.env.OTEL_SERVICE_NAME ||\n process.env.DATABRICKS_APP_NAME ||\n TelemetryManager.DEFAULT_FALLBACK_APP_NAME;\n const initialResource = resourceFromAttributes({\n [ATTR_SERVICE_NAME]: serviceName,\n [ATTR_SERVICE_VERSION]: config.serviceVersion ?? undefined,\n });\n const detectedResource = detectResources({\n detectors: [envDetector, hostDetector, processDetector],\n });\n return initialResource.merge(detectedResource);\n }\n\n private getDefaultInstrumentations(): Instrumentation[] {\n return [\n ...getNodeAutoInstrumentations({\n //\n // enabled as a part of the server plugin\n //\n \"@opentelemetry/instrumentation-http\": {\n enabled: false,\n },\n \"@opentelemetry/instrumentation-express\": {\n enabled: false,\n },\n //\n // reduce noise\n //\n \"@opentelemetry/instrumentation-fs\": {\n enabled: false,\n },\n \"@opentelemetry/instrumentation-dns\": {\n enabled: false,\n },\n \"@opentelemetry/instrumentation-net\": {\n enabled: false,\n },\n }),\n ];\n }\n\n private registerShutdown() {\n const shutdownFn = async () => {\n await TelemetryManager.getInstance().shutdown();\n };\n process.once(\"SIGTERM\", shutdownFn);\n process.once(\"SIGINT\", shutdownFn);\n }\n\n private async shutdown(): Promise<void> {\n if (!this.sdk) {\n return;\n }\n\n try {\n await this.sdk.shutdown();\n this.sdk = undefined;\n } catch (error) {\n logger.error(\"Error shutting down: %O\", error);\n }\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;AA6BA,MAAM,SAAS,aAAa,YAAY;AAExC,IAAa,mBAAb,MAAa,iBAAiB;CAC5B,OAAwB,6BAA6B;CACrD,OAAwB,4BAA4B;CAEpD,OAAe;CACf,AAAQ;;;;;;;;CASR,OAAO,YACL,YACA,iBACmB;AAEnB,SAAO,IAAI,kBAAkB,YADP,iBAAiB,aAAa,EACI,gBAAgB;;CAG1E,AAAQ,cAAc;CAEtB,OAAO,cAAgC;AACrC,MAAI,CAAC,iBAAiB,SACpB,kBAAiB,WAAW,IAAI,kBAAkB;AAEpD,SAAO,iBAAiB;;CAG1B,OAAO,WAAW,SAAmC,EAAE,EAAQ;AAE7D,EADiB,iBAAiB,aAAa,CACtC,YAAY,OAAO;;CAG9B,AAAQ,YAAY,QAAwC;AAC1D,MAAI,KAAK,IAAK;AAEd,MAAI,CAAC,QAAQ,IAAI,4BACf;AAGF,MAAI;AACF,QAAK,MAAM,IAAI,QAAQ;IACrB,UAAU,KAAK,eAAe,OAAO;IACrC,qBAAqB;IACrB,SAAS,IAAI,eAAe;IAC5B,eAAe,IAAI,kBAAkB,EAAE,SAAS,OAAO,SAAS,CAAC;IACjE,eAAe,CACb,IAAI,8BAA8B;KAChC,UAAU,IAAI,mBAAmB,EAAE,SAAS,OAAO,SAAS,CAAC;KAC7D,sBACE,OAAO,oBACP,iBAAiB;KACpB,CAAC,CACH;IACD,qBAAqB,CACnB,IAAI,wBACF,IAAI,gBAAgB,EAAE,SAAS,OAAO,SAAS,CAAC,CACjD,CACF;IACD,kBAAkB,KAAK,4BAA4B;IACpD,CAAC;AAEF,QAAK,IAAI,OAAO;AAChB,QAAK,kBAAkB;AACvB,UAAO,MAAM,2BAA2B;WACjC,OAAO;AACd,UAAO,MAAM,4BAA4B,MAAM;;;;;;;;CASnD,yBAAyB,kBAA2C;AAClE,2BAA6B,EAE3B,kBACD,CAAC;;CAGJ,AAAQ,eAAe,QAA4C;EACjE,MAAM,cACJ,OAAO,eACP,QAAQ,IAAI,qBACZ,QAAQ,IAAI,uBACZ,iBAAiB;EACnB,MAAM,kBAAkB,uBAAuB;IAC5C,oBAAoB;IACpB,uBAAuB,OAAO,kBAAkB;GAClD,CAAC;EACF,MAAM,mBAAmB,gBAAgB,EACvC,WAAW;GAAC;GAAa;GAAc;GAAgB,EACxD,CAAC;AACF,SAAO,gBAAgB,MAAM,iBAAiB;;CAGhD,AAAQ,6BAAgD;AACtD,SAAO,CACL,GAAG,4BAA4B;GAI7B,uCAAuC,EACrC,SAAS,OACV;GACD,0CAA0C,EACxC,SAAS,OACV;GAID,qCAAqC,EACnC,SAAS,OACV;GACD,sCAAsC,EACpC,SAAS,OACV;GACD,sCAAsC,EACpC,SAAS,OACV;GACF,CAAC,CACH;;CAGH,AAAQ,mBAAmB;EACzB,MAAM,aAAa,YAAY;AAC7B,SAAM,iBAAiB,aAAa,CAAC,UAAU;;AAEjD,UAAQ,KAAK,WAAW,WAAW;AACnC,UAAQ,KAAK,UAAU,WAAW;;CAGpC,MAAc,WAA0B;AACtC,MAAI,CAAC,KAAK,IACR;AAGF,MAAI;AACF,SAAM,KAAK,IAAI,UAAU;AACzB,QAAK,MAAM;WACJ,OAAO;AACd,UAAO,MAAM,2BAA2B,MAAM"}
1
+ {"version":3,"file":"telemetry-manager.js","names":[],"sources":["../../src/telemetry/telemetry-manager.ts"],"sourcesContent":["import { getNodeAutoInstrumentations } from \"@opentelemetry/auto-instrumentations-node\";\nimport { OTLPLogExporter } from \"@opentelemetry/exporter-logs-otlp-proto\";\nimport { OTLPMetricExporter } from \"@opentelemetry/exporter-metrics-otlp-proto\";\nimport { OTLPTraceExporter } from \"@opentelemetry/exporter-trace-otlp-proto\";\nimport {\n type Instrumentation,\n registerInstrumentations as otelRegisterInstrumentations,\n} from \"@opentelemetry/instrumentation\";\nimport {\n detectResources,\n envDetector,\n hostDetector,\n processDetector,\n type Resource,\n resourceFromAttributes,\n} from \"@opentelemetry/resources\";\nimport { BatchLogRecordProcessor } from \"@opentelemetry/sdk-logs\";\nimport { PeriodicExportingMetricReader } from \"@opentelemetry/sdk-metrics\";\nimport { NodeSDK } from \"@opentelemetry/sdk-node\";\nimport {\n ATTR_SERVICE_NAME,\n ATTR_SERVICE_VERSION,\n} from \"@opentelemetry/semantic-conventions\";\nimport type { TelemetryOptions } from \"shared\";\nimport { createLogger } from \"../logging/logger\";\nimport { TelemetryProvider } from \"./telemetry-provider\";\nimport { AppKitSampler } from \"./trace-sampler\";\nimport type { TelemetryConfig } from \"./types\";\n\nconst logger = createLogger(\"telemetry\");\n\nexport class TelemetryManager {\n private static readonly DEFAULT_EXPORT_INTERVAL_MS = 10000;\n private static readonly DEFAULT_FALLBACK_APP_NAME = \"databricks-app\";\n\n private static instance?: TelemetryManager;\n private sdk?: NodeSDK;\n private shutdownPromise?: Promise<void>;\n\n /**\n * Create a scoped telemetry provider for a specific plugin.\n * The plugin's name will be used as the default tracer/meter name.\n * @param pluginName - The name of the plugin to create scoped telemetry for\n * @param telemetryConfig - The telemetry configuration for the plugin\n * @returns A scoped telemetry instance for the plugin\n */\n static getProvider(\n pluginName: string,\n telemetryConfig?: TelemetryOptions,\n ): TelemetryProvider {\n const globalManager = TelemetryManager.getInstance();\n return new TelemetryProvider(pluginName, globalManager, telemetryConfig);\n }\n\n private constructor() {}\n\n static getInstance(): TelemetryManager {\n if (!TelemetryManager.instance) {\n TelemetryManager.instance = new TelemetryManager();\n }\n return TelemetryManager.instance;\n }\n\n static initialize(config: Partial<TelemetryConfig> = {}): void {\n const instance = TelemetryManager.getInstance();\n instance._initialize(config);\n }\n\n private _initialize(config: Partial<TelemetryConfig>): void {\n if (this.sdk) return;\n\n if (!process.env.OTEL_EXPORTER_OTLP_ENDPOINT) {\n return;\n }\n\n try {\n this.sdk = new NodeSDK({\n resource: this.createResource(config),\n autoDetectResources: false,\n sampler: new AppKitSampler(),\n traceExporter: new OTLPTraceExporter({ headers: config.headers }),\n metricReaders: [\n new PeriodicExportingMetricReader({\n exporter: new OTLPMetricExporter({ headers: config.headers }),\n exportIntervalMillis:\n config.exportIntervalMs ||\n TelemetryManager.DEFAULT_EXPORT_INTERVAL_MS,\n }),\n ],\n logRecordProcessors: [\n new BatchLogRecordProcessor(\n new OTLPLogExporter({ headers: config.headers }),\n ),\n ],\n instrumentations: this.getDefaultInstrumentations(),\n });\n\n this.sdk.start();\n logger.debug(\"Initialized successfully\");\n } catch (error) {\n logger.error(\"Failed to initialize: %O\", error);\n }\n }\n\n /**\n * Register OpenTelemetry instrumentations.\n * Can be called at any time, but recommended to call in plugin constructor.\n * @param instrumentations - Array of OpenTelemetry instrumentations to register\n */\n registerInstrumentations(instrumentations: Instrumentation[]): void {\n otelRegisterInstrumentations({\n // global providers set by NodeSDK.start()\n instrumentations,\n });\n }\n\n private createResource(config: Partial<TelemetryConfig>): Resource {\n const serviceName =\n config.serviceName ||\n process.env.OTEL_SERVICE_NAME ||\n process.env.DATABRICKS_APP_NAME ||\n TelemetryManager.DEFAULT_FALLBACK_APP_NAME;\n const initialResource = resourceFromAttributes({\n [ATTR_SERVICE_NAME]: serviceName,\n [ATTR_SERVICE_VERSION]: config.serviceVersion ?? undefined,\n });\n const detectedResource = detectResources({\n detectors: [envDetector, hostDetector, processDetector],\n });\n return initialResource.merge(detectedResource);\n }\n\n private getDefaultInstrumentations(): Instrumentation[] {\n return [\n ...getNodeAutoInstrumentations({\n //\n // enabled as a part of the server plugin\n //\n \"@opentelemetry/instrumentation-http\": {\n enabled: false,\n },\n \"@opentelemetry/instrumentation-express\": {\n enabled: false,\n },\n //\n // reduce noise\n //\n \"@opentelemetry/instrumentation-fs\": {\n enabled: false,\n },\n \"@opentelemetry/instrumentation-dns\": {\n enabled: false,\n },\n \"@opentelemetry/instrumentation-net\": {\n enabled: false,\n },\n }),\n ];\n }\n\n /**\n * Flush and shut down the OpenTelemetry SDK.\n *\n * Idempotent: the SDK reference is cleared synchronously and concurrent\n * or repeated calls await the same in-flight flush. Awaited by the core\n * lifecycle manager during graceful shutdown — that manager owns the\n * process signal handlers, so telemetry no longer registers its own.\n */\n async shutdown(): Promise<void> {\n if (this.sdk) {\n const sdk = this.sdk;\n this.sdk = undefined;\n this.shutdownPromise = (async () => {\n try {\n await sdk.shutdown();\n } catch (error) {\n logger.error(\"Error shutting down: %O\", error);\n }\n })();\n }\n\n return this.shutdownPromise;\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;AA6BA,MAAM,SAAS,aAAa,YAAY;AAExC,IAAa,mBAAb,MAAa,iBAAiB;CAC5B,OAAwB,6BAA6B;CACrD,OAAwB,4BAA4B;CAEpD,OAAe;CACf,AAAQ;CACR,AAAQ;;;;;;;;CASR,OAAO,YACL,YACA,iBACmB;AAEnB,SAAO,IAAI,kBAAkB,YADP,iBAAiB,aAAa,EACI,gBAAgB;;CAG1E,AAAQ,cAAc;CAEtB,OAAO,cAAgC;AACrC,MAAI,CAAC,iBAAiB,SACpB,kBAAiB,WAAW,IAAI,kBAAkB;AAEpD,SAAO,iBAAiB;;CAG1B,OAAO,WAAW,SAAmC,EAAE,EAAQ;AAE7D,EADiB,iBAAiB,aAAa,CACtC,YAAY,OAAO;;CAG9B,AAAQ,YAAY,QAAwC;AAC1D,MAAI,KAAK,IAAK;AAEd,MAAI,CAAC,QAAQ,IAAI,4BACf;AAGF,MAAI;AACF,QAAK,MAAM,IAAI,QAAQ;IACrB,UAAU,KAAK,eAAe,OAAO;IACrC,qBAAqB;IACrB,SAAS,IAAI,eAAe;IAC5B,eAAe,IAAI,kBAAkB,EAAE,SAAS,OAAO,SAAS,CAAC;IACjE,eAAe,CACb,IAAI,8BAA8B;KAChC,UAAU,IAAI,mBAAmB,EAAE,SAAS,OAAO,SAAS,CAAC;KAC7D,sBACE,OAAO,oBACP,iBAAiB;KACpB,CAAC,CACH;IACD,qBAAqB,CACnB,IAAI,wBACF,IAAI,gBAAgB,EAAE,SAAS,OAAO,SAAS,CAAC,CACjD,CACF;IACD,kBAAkB,KAAK,4BAA4B;IACpD,CAAC;AAEF,QAAK,IAAI,OAAO;AAChB,UAAO,MAAM,2BAA2B;WACjC,OAAO;AACd,UAAO,MAAM,4BAA4B,MAAM;;;;;;;;CASnD,yBAAyB,kBAA2C;AAClE,2BAA6B,EAE3B,kBACD,CAAC;;CAGJ,AAAQ,eAAe,QAA4C;EACjE,MAAM,cACJ,OAAO,eACP,QAAQ,IAAI,qBACZ,QAAQ,IAAI,uBACZ,iBAAiB;EACnB,MAAM,kBAAkB,uBAAuB;IAC5C,oBAAoB;IACpB,uBAAuB,OAAO,kBAAkB;GAClD,CAAC;EACF,MAAM,mBAAmB,gBAAgB,EACvC,WAAW;GAAC;GAAa;GAAc;GAAgB,EACxD,CAAC;AACF,SAAO,gBAAgB,MAAM,iBAAiB;;CAGhD,AAAQ,6BAAgD;AACtD,SAAO,CACL,GAAG,4BAA4B;GAI7B,uCAAuC,EACrC,SAAS,OACV;GACD,0CAA0C,EACxC,SAAS,OACV;GAID,qCAAqC,EACnC,SAAS,OACV;GACD,sCAAsC,EACpC,SAAS,OACV;GACD,sCAAsC,EACpC,SAAS,OACV;GACF,CAAC,CACH;;;;;;;;;;CAWH,MAAM,WAA0B;AAC9B,MAAI,KAAK,KAAK;GACZ,MAAM,MAAM,KAAK;AACjB,QAAK,MAAM;AACX,QAAK,mBAAmB,YAAY;AAClC,QAAI;AACF,WAAM,IAAI,UAAU;aACb,OAAO;AACd,YAAO,MAAM,2BAA2B,MAAM;;OAE9C;;AAGN,SAAO,KAAK"}
@@ -0,0 +1,28 @@
1
+ //#region src/utils/safe-handler.ts
2
+ /**
3
+ * Wrap an Express handler so synchronous throws and async rejections are
4
+ * forwarded to the error-handling middleware via `next(err)`.
5
+ *
6
+ * Express 4 does not catch rejected promises from async handlers — a
7
+ * rejection (e.g. `asUser()` throwing `AuthenticationError` before any
8
+ * response is written) escapes as an unhandledRejection, which leaves the
9
+ * request hanging and crashes Node by default.
10
+ *
11
+ * Error-handling middleware (arity 4, `(err, req, res, next)`) is returned
12
+ * unchanged: Express identifies it by function arity, so wrapping it would
13
+ * silently turn it into a regular handler.
14
+ *
15
+ * Note: handlers must RETURN their promise (e.g. `(req, res) =>
16
+ * this._handle(req, res)` or `async (req, res) => { await ... }`) for
17
+ * rejections to be caught — a fire-and-forget body like
18
+ * `(req, res) => { this._handle(req, res); }` hides the promise from the
19
+ * wrapper and rejections escape as unhandledRejection again.
20
+ */
21
+ function forwardAsyncErrors(handler) {
22
+ if (handler.length > 3) return handler;
23
+ return (req, res, next) => Promise.resolve().then(() => handler(req, res, next)).catch(next);
24
+ }
25
+
26
+ //#endregion
27
+ export { forwardAsyncErrors };
28
+ //# sourceMappingURL=safe-handler.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"safe-handler.js","names":[],"sources":["../../src/utils/safe-handler.ts"],"sourcesContent":["import type express from \"express\";\n\n/**\n * Wrap an Express handler so synchronous throws and async rejections are\n * forwarded to the error-handling middleware via `next(err)`.\n *\n * Express 4 does not catch rejected promises from async handlers — a\n * rejection (e.g. `asUser()` throwing `AuthenticationError` before any\n * response is written) escapes as an unhandledRejection, which leaves the\n * request hanging and crashes Node by default.\n *\n * Error-handling middleware (arity 4, `(err, req, res, next)`) is returned\n * unchanged: Express identifies it by function arity, so wrapping it would\n * silently turn it into a regular handler.\n *\n * Note: handlers must RETURN their promise (e.g. `(req, res) =>\n * this._handle(req, res)` or `async (req, res) => { await ... }`) for\n * rejections to be caught — a fire-and-forget body like\n * `(req, res) => { this._handle(req, res); }` hides the promise from the\n * wrapper and rejections escape as unhandledRejection again.\n */\nexport function forwardAsyncErrors(\n handler: (\n req: express.Request,\n res: express.Response,\n next: express.NextFunction,\n ) => unknown,\n): express.RequestHandler {\n if (handler.length > 3) return handler as express.RequestHandler;\n return (req, res, next) =>\n Promise.resolve()\n .then(() => handler(req, res, next))\n .catch(next);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAqBA,SAAgB,mBACd,SAKwB;AACxB,KAAI,QAAQ,SAAS,EAAG,QAAO;AAC/B,SAAQ,KAAK,KAAK,SAChB,QAAQ,SAAS,CACd,WAAW,QAAQ,KAAK,KAAK,KAAK,CAAC,CACnC,MAAM,KAAK"}
@@ -224,6 +224,8 @@ abortActiveOperations(): void;
224
224
 
225
225
  ```
226
226
 
227
+ Cancel in-flight work (abort signals, SSE streams). Runs in the first phase of graceful shutdown, BEFORE any plugin's `shutdown()` hook — so it must not tear down shared resources (e.g. connection pools) that other plugins' hooks may still need. Put teardown in `shutdown()`.
228
+
227
229
  #### Returns[​](#returns-1 "Direct link to Returns")
228
230
 
229
231
  `void`
@@ -249,7 +249,7 @@ const registry = ResourceRegistry.getInstance();
249
249
  const result = registry.validate();
250
250
 
251
251
  if (!result.valid) {
252
- console.error("Missing resources:", result.missing.map(r => Object.values(r.fields).map(f => f.env)));
252
+ console.error(ResourceRegistry.formatMissingResources(result.missing));
253
253
  }
254
254
 
255
255
  ```
@@ -35,6 +35,17 @@ When true, the thread used for a chat request against this agent is deleted from
35
35
 
36
36
  ***
37
37
 
38
+ ### generationParams?[​](#generationparams "Direct link to generationParams?")
39
+
40
+ ```ts
41
+ optional generationParams: GenerationParams;
42
+
43
+ ```
44
+
45
+ Optional generation parameters (`temperature`, `top_p`, `stop`, `frequency_penalty`, `presence_penalty`) forwarded to the OpenAI-compatible serving request body. Only set keys are sent. Applied only when AppKit builds the adapter itself (string or omitted `model`); when you pass a pre-built `AgentAdapter`, configure generation params on it directly.
46
+
47
+ ***
48
+
38
49
  ### instructions[​](#instructions "Direct link to instructions")
39
50
 
40
51
  ```ts
@@ -0,0 +1,58 @@
1
+ # Interface: GenerationParams
2
+
3
+ Optional generation parameters forwarded to the OpenAI-compatible serving request body. Names match the serving API wire keys. Only keys that are set are sent — undefined values are omitted so the endpoint applies its own defaults. Ranges are not validated here; the serving endpoint validates.
4
+
5
+ ## Properties[​](#properties "Direct link to Properties")
6
+
7
+ ### frequency\_penalty?[​](#frequency_penalty "Direct link to frequency_penalty?")
8
+
9
+ ```ts
10
+ optional frequency_penalty: number;
11
+
12
+ ```
13
+
14
+ Penalize tokens by frequency.
15
+
16
+ ***
17
+
18
+ ### presence\_penalty?[​](#presence_penalty "Direct link to presence_penalty?")
19
+
20
+ ```ts
21
+ optional presence_penalty: number;
22
+
23
+ ```
24
+
25
+ Penalize tokens by prior presence.
26
+
27
+ ***
28
+
29
+ ### stop?[​](#stop "Direct link to stop?")
30
+
31
+ ```ts
32
+ optional stop: string | string[];
33
+
34
+ ```
35
+
36
+ Stop sequence(s) that end generation.
37
+
38
+ ***
39
+
40
+ ### temperature?[​](#temperature "Direct link to temperature?")
41
+
42
+ ```ts
43
+ optional temperature: number;
44
+
45
+ ```
46
+
47
+ Sampling temperature.
48
+
49
+ ***
50
+
51
+ ### top\_p?[​](#top_p "Direct link to top_p?")
52
+
53
+ ```ts
54
+ optional top_p: number;
55
+
56
+ ```
57
+
58
+ Nucleus sampling probability mass (`top_p`).
@@ -31,6 +31,17 @@ Mirrors `AgentDefinition.ephemeral` — skip thread persistence.
31
31
 
32
32
  ***
33
33
 
34
+ ### generationParams?[​](#generationparams "Direct link to generationParams?")
35
+
36
+ ```ts
37
+ optional generationParams: GenerationParams;
38
+
39
+ ```
40
+
41
+ Mirrors `AgentDefinition.generationParams`.
42
+
43
+ ***
44
+
34
45
  ### instructions[​](#instructions "Direct link to instructions")
35
46
 
36
47
  ```ts
@@ -47,6 +47,7 @@ Documentation merge entry for Typedoc — combines the stable `@databricks/appki
47
47
  | [FileResource](./docs/api/appkit/Interface.FileResource.md) | Describes the file or directory being acted upon. |
48
48
  | [FunctionTool](./docs/api/appkit/Interface.FunctionTool.md) | - |
49
49
  | [GenerateDatabaseCredentialRequest](./docs/api/appkit/Interface.GenerateDatabaseCredentialRequest.md) | Request parameters for generating database OAuth credentials |
50
+ | [GenerationParams](./docs/api/appkit/Interface.GenerationParams.md) | Optional generation parameters forwarded to the OpenAI-compatible serving request body. Names match the serving API wire keys. Only keys that are set are sent — undefined values are omitted so the endpoint applies its own defaults. Ranges are not validated here; the serving endpoint validates. |
50
51
  | [IJobsConfig](./docs/api/appkit/Interface.IJobsConfig.md) | Configuration for the Jobs plugin. |
51
52
  | [ITelemetry](./docs/api/appkit/Interface.ITelemetry.md) | Plugin-facing interface for OpenTelemetry instrumentation. Provides a thin abstraction over OpenTelemetry APIs for plugins. |
52
53
  | [JobAPI](./docs/api/appkit/Interface.JobAPI.md) | User-facing API for a single configured job. |
package/llms.txt CHANGED
@@ -125,6 +125,7 @@ npx @databricks/appkit docs <query>
125
125
  - [Interface: FileResource](./docs/api/appkit/Interface.FileResource.md): Describes the file or directory being acted upon.
126
126
  - [Interface: FunctionTool](./docs/api/appkit/Interface.FunctionTool.md): Properties
127
127
  - [Interface: GenerateDatabaseCredentialRequest](./docs/api/appkit/Interface.GenerateDatabaseCredentialRequest.md): Request parameters for generating database OAuth credentials
128
+ - [Interface: GenerationParams](./docs/api/appkit/Interface.GenerationParams.md): Optional generation parameters forwarded to the OpenAI-compatible serving
128
129
  - [Interface: IJobsConfig](./docs/api/appkit/Interface.IJobsConfig.md): Configuration for the Jobs plugin.
129
130
  - [Interface: ITelemetry](./docs/api/appkit/Interface.ITelemetry.md): Plugin-facing interface for OpenTelemetry instrumentation.
130
131
  - [Interface: JobAPI](./docs/api/appkit/Interface.JobAPI.md): User-facing API for a single configured job.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@databricks/appkit",
3
3
  "type": "module",
4
- "version": "0.45.0",
4
+ "version": "0.46.0",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
7
7
  "bin": {