@kindgi/handler-runtime 0.0.0-bootstrap.0 → 0.1.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 (68) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +218 -2
  3. package/dist/build-extensions.d.ts +75 -0
  4. package/dist/build-extensions.d.ts.map +1 -0
  5. package/dist/build-extensions.js +27 -0
  6. package/dist/build-extensions.js.map +1 -0
  7. package/dist/discovery.d.ts +19 -0
  8. package/dist/discovery.d.ts.map +1 -0
  9. package/dist/discovery.js +81 -0
  10. package/dist/discovery.js.map +1 -0
  11. package/dist/entrypoint.d.ts +10 -0
  12. package/dist/entrypoint.d.ts.map +1 -0
  13. package/dist/entrypoint.js +24 -0
  14. package/dist/entrypoint.js.map +1 -0
  15. package/dist/handler-runner.d.ts +127 -0
  16. package/dist/handler-runner.d.ts.map +1 -0
  17. package/dist/handler-runner.js +318 -0
  18. package/dist/handler-runner.js.map +1 -0
  19. package/dist/index.d.ts +8 -0
  20. package/dist/index.d.ts.map +1 -0
  21. package/dist/index.js +7 -0
  22. package/dist/index.js.map +1 -0
  23. package/dist/kindgi-index-main.d.ts +2 -0
  24. package/dist/kindgi-index-main.d.ts.map +1 -0
  25. package/dist/kindgi-index-main.js +15 -0
  26. package/dist/kindgi-index-main.js.map +1 -0
  27. package/dist/kindgi-index.d.ts +316 -0
  28. package/dist/kindgi-index.d.ts.map +1 -0
  29. package/dist/kindgi-index.js +1201 -0
  30. package/dist/kindgi-index.js.map +1 -0
  31. package/dist/pack-env.d.ts +66 -0
  32. package/dist/pack-env.d.ts.map +1 -0
  33. package/dist/pack-env.js +97 -0
  34. package/dist/pack-env.js.map +1 -0
  35. package/dist/pack-service/index.d.ts +7 -0
  36. package/dist/pack-service/index.d.ts.map +1 -0
  37. package/dist/pack-service/index.js +6 -0
  38. package/dist/pack-service/index.js.map +1 -0
  39. package/dist/pack-service/main.d.ts +47 -0
  40. package/dist/pack-service/main.d.ts.map +1 -0
  41. package/dist/pack-service/main.js +201 -0
  42. package/dist/pack-service/main.js.map +1 -0
  43. package/dist/pack-service/service.d.ts +61 -0
  44. package/dist/pack-service/service.d.ts.map +1 -0
  45. package/dist/pack-service/service.js +341 -0
  46. package/dist/pack-service/service.js.map +1 -0
  47. package/dist/pack-service/supervisor.d.ts +138 -0
  48. package/dist/pack-service/supervisor.d.ts.map +1 -0
  49. package/dist/pack-service/supervisor.js +424 -0
  50. package/dist/pack-service/supervisor.js.map +1 -0
  51. package/dist/protocol.d.ts +104 -0
  52. package/dist/protocol.d.ts.map +1 -0
  53. package/dist/protocol.js +116 -0
  54. package/dist/protocol.js.map +1 -0
  55. package/package.json +87 -4
  56. package/src/build-extensions.ts +100 -0
  57. package/src/discovery.ts +89 -0
  58. package/src/entrypoint.ts +23 -0
  59. package/src/handler-runner.ts +500 -0
  60. package/src/index.ts +66 -0
  61. package/src/kindgi-index-main.ts +17 -0
  62. package/src/kindgi-index.ts +1605 -0
  63. package/src/pack-env.ts +148 -0
  64. package/src/pack-service/index.ts +17 -0
  65. package/src/pack-service/main.ts +246 -0
  66. package/src/pack-service/service.ts +478 -0
  67. package/src/pack-service/supervisor.ts +600 -0
  68. package/src/protocol.ts +214 -0
@@ -0,0 +1,116 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ // Copyright (C) 2026 Kindgi Inc.
3
+ /**
4
+ * Pack protocol v2 — the messages a runtime exchanges with a pack
5
+ * service (`./pack-service`) to run pack code: tool handlers and
6
+ * guardrail checks.
7
+ *
8
+ * A request names a tool or check by id; the pack service resolves it
9
+ * from its own `index.json`. The caller never chooses which module is
10
+ * imported, and the schemas that validate input and output are the
11
+ * ones the pack was built with.
12
+ *
13
+ * One request, one response. Every outcome of running pack code —
14
+ * success, a validation failure, a throw, a deadline, an unknown tool —
15
+ * is a response message. Transport problems (auth, overload, draining)
16
+ * are HTTP statuses, never messages.
17
+ */
18
+ /** Wire version of every v2 message. */
19
+ export const PACK_PROTOCOL_VERSION = 2;
20
+ export function packError(code, message, extra = {}) {
21
+ return { v: PACK_PROTOCOL_VERSION, kind: 'error', code, message, ...extra };
22
+ }
23
+ function isRecord(value) {
24
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
25
+ }
26
+ function checkEnvelope(value) {
27
+ if (!isRecord(value)) {
28
+ return { kind: 'err', error: packError('malformed-message', 'Message must be a JSON object') };
29
+ }
30
+ if (value.v !== PACK_PROTOCOL_VERSION) {
31
+ return {
32
+ kind: 'err',
33
+ error: packError('unknown-protocol-version', `Expected protocol v${PACK_PROTOCOL_VERSION}, got ${JSON.stringify(value.v)}`),
34
+ };
35
+ }
36
+ return { kind: 'ok', value };
37
+ }
38
+ function parseContext(raw) {
39
+ if (!isRecord(raw))
40
+ return undefined;
41
+ if (typeof raw.tenantId !== 'string' || typeof raw.runId !== 'string')
42
+ return undefined;
43
+ return raw;
44
+ }
45
+ /** Parse a request body (already JSON-decoded) into a v2 request. */
46
+ export function parsePackRequest(value) {
47
+ const env = checkEnvelope(value);
48
+ if (env.kind === 'err')
49
+ return env;
50
+ const m = env.value;
51
+ if (m.kind === 'invoke')
52
+ return parseToolInvoke(m);
53
+ if (m.kind === 'check-invoke')
54
+ return parseCheckInvoke(m);
55
+ return {
56
+ kind: 'err',
57
+ error: packError('unexpected-message-kind', `Unknown request kind ${JSON.stringify(m.kind)}`),
58
+ };
59
+ }
60
+ function malformed(message) {
61
+ return { kind: 'err', error: packError('malformed-message', message) };
62
+ }
63
+ function parseToolInvoke(m) {
64
+ const tool = m.tool;
65
+ if (!isRecord(tool) || typeof tool.id !== 'string' || tool.id.length === 0) {
66
+ return malformed('`tool.id` is required');
67
+ }
68
+ if (tool.version !== undefined && typeof tool.version !== 'string') {
69
+ return malformed('`tool.version` must be a string');
70
+ }
71
+ if (parseContext(m.ctx) === undefined)
72
+ return malformed('`ctx` needs string `tenantId` and `runId`');
73
+ return { kind: 'ok', value: m };
74
+ }
75
+ function parseCheckInvoke(m) {
76
+ const check = m.check;
77
+ if (!isRecord(check) || typeof check.id !== 'string' || check.id.length === 0) {
78
+ return malformed('`check.id` is required');
79
+ }
80
+ if (!isRecord(m.config))
81
+ return malformed('`config` must be an object');
82
+ return { kind: 'ok', value: m };
83
+ }
84
+ /** Parse a response body (already JSON-decoded) into a v2 response. */
85
+ export function parsePackResponse(value) {
86
+ const env = checkEnvelope(value);
87
+ if (env.kind === 'err')
88
+ return env;
89
+ const m = env.value;
90
+ if (m.kind === 'result' || m.kind === 'check-result') {
91
+ return { kind: 'ok', value: m };
92
+ }
93
+ if (m.kind === 'error' && typeof m.code === 'string' && typeof m.message === 'string') {
94
+ return { kind: 'ok', value: m };
95
+ }
96
+ return {
97
+ kind: 'err',
98
+ error: packError('unexpected-message-kind', `Unknown response kind ${JSON.stringify(m.kind)}`),
99
+ };
100
+ }
101
+ /**
102
+ * HTTP headers of the v2 transport. `Authorization` is deliberately not
103
+ * used: on platforms like Cloud Run it carries the platform's identity
104
+ * token, so the pack token travels separately.
105
+ */
106
+ export const PACK_HEADERS = {
107
+ protocol: 'kindgi-protocol',
108
+ token: 'kindgi-pack-token',
109
+ /** Relative deadline for the call, in milliseconds. */
110
+ timeoutMs: 'kindgi-timeout-ms',
111
+ runId: 'kindgi-run-id',
112
+ requestId: 'kindgi-request-id',
113
+ artifactVersion: 'kindgi-artifact-version',
114
+ durationMs: 'kindgi-duration-ms',
115
+ };
116
+ //# sourceMappingURL=protocol.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"protocol.js","sourceRoot":"","sources":["../src/protocol.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,7 +1,90 @@
1
1
  {
2
2
  "name": "@kindgi/handler-runtime",
3
- "version": "0.0.0-bootstrap.0",
4
- "description": "Placeholder so a trusted publisher can be attached. Releases are published from https://github.com/kindgi/kindgi-sdk with provenance; use 0.1.0 or later.",
3
+ "version": "0.1.0",
4
+ "description": "Runtime-side primitives for running a Kindgi pack's own code. Ships the pack service (`pack-service`, process entry `pack-service-main`) — a long-lived HTTP server that runs a pack's tool handlers and guardrail checks over pack protocol v2 (`protocol`), validating each tool call's input and output against the tool's JSON Schemas, with a local supervisor for development; and the build-time pack indexer (`kindgi-index`, process entry `kindgi-index-main`), which writes the pack's `index.json`.",
5
5
  "license": "Apache-2.0",
6
- "repository": { "type": "git", "url": "git+https://github.com/kindgi/kindgi-sdk.git" }
7
- }
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/kindgi/kindgi-sdk.git",
9
+ "directory": "packages/handler-runtime"
10
+ },
11
+ "homepage": "https://github.com/kindgi/kindgi-sdk/tree/main/packages/handler-runtime#readme",
12
+ "bugs": {
13
+ "url": "https://github.com/kindgi/kindgi-sdk/issues"
14
+ },
15
+ "type": "module",
16
+ "main": "./dist/index.js",
17
+ "types": "./dist/index.d.ts",
18
+ "exports": {
19
+ ".": {
20
+ "types": "./dist/index.d.ts",
21
+ "import": "./dist/index.js"
22
+ },
23
+ "./protocol": {
24
+ "types": "./dist/protocol.d.ts",
25
+ "import": "./dist/protocol.js"
26
+ },
27
+ "./pack-service": {
28
+ "types": "./dist/pack-service/index.d.ts",
29
+ "import": "./dist/pack-service/index.js"
30
+ },
31
+ "./pack-service-main": {
32
+ "types": "./dist/pack-service/main.d.ts",
33
+ "import": "./dist/pack-service/main.js"
34
+ },
35
+ "./kindgi-index": {
36
+ "types": "./dist/kindgi-index.d.ts",
37
+ "import": "./dist/kindgi-index.js"
38
+ },
39
+ "./kindgi-index-main": {
40
+ "types": "./dist/kindgi-index-main.d.ts",
41
+ "import": "./dist/kindgi-index-main.js"
42
+ },
43
+ "./build-extensions": {
44
+ "types": "./dist/build-extensions.d.ts",
45
+ "import": "./dist/build-extensions.js"
46
+ }
47
+ },
48
+ "files": [
49
+ "dist",
50
+ "src",
51
+ "README.md"
52
+ ],
53
+ "dependencies": {
54
+ "@kindgi/flow": "0.1.0",
55
+ "@kindgi/sandbox": "0.1.0",
56
+ "@kindgi/schema": "0.1.0",
57
+ "@kindgi/types": "0.1.0",
58
+ "ajv": "^8.17.1",
59
+ "ajv-formats": "^3.0.1",
60
+ "smol-toml": "^1.9.0"
61
+ },
62
+ "peerDependencies": {
63
+ "zod": "^4.0.0"
64
+ },
65
+ "peerDependenciesMeta": {
66
+ "zod": {
67
+ "optional": true
68
+ }
69
+ },
70
+ "devDependencies": {
71
+ "@kindgi/specs": "0.1.0",
72
+ "@types/node": "^22.10.5",
73
+ "typescript": "^5.7.3",
74
+ "vitest": "^2.1.8",
75
+ "zod": "^4.6.5"
76
+ },
77
+ "engines": {
78
+ "node": ">=22.0.0"
79
+ },
80
+ "publishConfig": {
81
+ "access": "public",
82
+ "provenance": true
83
+ },
84
+ "scripts": {
85
+ "build": "tsc -p tsconfig.build.json",
86
+ "typecheck": "tsc --noEmit",
87
+ "test": "vitest run",
88
+ "clean": "rm -rf dist *.tsbuildinfo"
89
+ }
90
+ }
@@ -0,0 +1,100 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ // Copyright (C) 2026 Kindgi Inc.
3
+
4
+ /**
5
+ * A TypeScript pack image's build extensions: what an app's image needs
6
+ * beyond installing the app's dependencies — Debian packages, a step
7
+ * that generates code after the install (Prisma's client), placeholder
8
+ * env for build steps. `image` in `kindgi.config.*`:
9
+ *
10
+ * ```ts
11
+ * import { prisma } from '@kindgi/sdk/build';
12
+ *
13
+ * export default {
14
+ * pack: { id: 'acme.app', version: '1.0.0' },
15
+ * image: {
16
+ * systemPackages: ['tesseract-ocr'],
17
+ * extensions: [prisma({ schema: 'prisma/schema.prisma', config: 'prisma.config.ts' })],
18
+ * buildEnv: { DATABASE_URL: 'postgresql://build-placeholder' },
19
+ * },
20
+ * };
21
+ * ```
22
+ *
23
+ * Data only: nothing here runs at build time. `kindgi build` renders it
24
+ * into the image's Containerfile — system packages in the base stage,
25
+ * `buildEnv` in the install stage only (never in the final image), each
26
+ * `postInstall` step after the install and before the prune to
27
+ * production, through the app's own package manager.
28
+ */
29
+
30
+ /** A package's bin, run through the app's package manager (`pnpm exec`, `npx --no`, `yarn`). */
31
+ export interface BuildStep {
32
+ readonly bin: string;
33
+ readonly args?: readonly string[];
34
+ }
35
+
36
+ export interface BuildExtension {
37
+ readonly name: string;
38
+ /**
39
+ * Files the steps read, relative to the pack folder: copied into the
40
+ * image's install stage, at the same paths.
41
+ */
42
+ readonly contextFiles?: readonly string[];
43
+ /** Debian packages the image needs. */
44
+ readonly systemPackages?: readonly string[];
45
+ /**
46
+ * Steps after the install, before the prune: dev dependencies (a
47
+ * generator's CLI) are still there. Run in the pack folder.
48
+ */
49
+ readonly postInstall?: readonly BuildStep[];
50
+ /**
51
+ * Env for the build steps, and for the indexer stage that imports the
52
+ * pack's modules: non-secret placeholders only. Never in the final
53
+ * image.
54
+ */
55
+ readonly buildEnv?: Readonly<Record<string, string>>;
56
+ }
57
+
58
+ /** `image` in `kindgi.config.*` (a TypeScript pack). */
59
+ export interface ImageConfig {
60
+ /** Debian packages the image needs (as a Python pack's `[tool.kindgi.image] system-packages`). */
61
+ readonly systemPackages?: readonly string[];
62
+ readonly extensions?: readonly BuildExtension[];
63
+ /** Env for the build steps, merged over the extensions' (see `BuildExtension.buildEnv`). */
64
+ readonly buildEnv?: Readonly<Record<string, string>>;
65
+ }
66
+
67
+ /** An app's own extension: the shape checked by the type, returned as is. */
68
+ export function defineBuildExtension(extension: BuildExtension): BuildExtension {
69
+ return extension;
70
+ }
71
+
72
+ export interface PrismaExtensionOptions {
73
+ /** The Prisma schema, relative to the pack folder. */
74
+ readonly schema: string;
75
+ /** The Prisma config file (`prisma.config.ts`), when the app has one; it names the schema. */
76
+ readonly config?: string;
77
+ }
78
+
79
+ /**
80
+ * Prisma's generated client in the image. The app's own `postinstall:
81
+ * prisma generate` doesn't run (the image installs with scripts off), so
82
+ * this copies the schema (and config) in and runs `prisma generate` after
83
+ * the install, with the app's `prisma` CLI.
84
+ */
85
+ export function prisma(options: PrismaExtensionOptions): BuildExtension {
86
+ return {
87
+ name: 'prisma',
88
+ contextFiles:
89
+ options.config !== undefined ? [options.schema, options.config] : [options.schema],
90
+ postInstall: [
91
+ {
92
+ bin: 'prisma',
93
+ args:
94
+ options.config !== undefined
95
+ ? ['generate', '--config', options.config]
96
+ : ['generate', '--schema', options.schema],
97
+ },
98
+ ],
99
+ };
100
+ }
@@ -0,0 +1,89 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ // Copyright (C) 2026 Kindgi Inc.
3
+
4
+ /**
5
+ * Discovery-glob primitives shared by the indexer and anything that
6
+ * watches a pack (a dev watch loop) — so "which files are primitives"
7
+ * has one answer.
8
+ *
9
+ * Supported syntax: `**` (any depth, including none), `*` (within one
10
+ * segment), `?` (one character), `{a,b,c}` alternation. Negations and
11
+ * extglobs are not supported.
12
+ *
13
+ * Every pattern has a **static prefix** — the directory segments before
14
+ * the first wildcard (`kindgi/tools/**\/*.ts` → `kindgi/tools`). Walking
15
+ * or watching only that prefix matters when a pack lives inside a large
16
+ * application: the indexer must not crawl the whole host repo to find
17
+ * `kindgi/**`.
18
+ */
19
+
20
+ const GLOB_CHARS = /[*?{[]/;
21
+
22
+ /** Test / spec files sit next to primitives and are never primitives. */
23
+ export const TEST_FILE_REGEX = /\.(test|spec)\.(ts|js|mjs|cjs)$/;
24
+
25
+ /** Compile a discovery pattern to an anchored RegExp over `/`-separated relative paths. */
26
+ export function globToRegex(pattern: string): RegExp {
27
+ let re = '';
28
+ let i = 0;
29
+ while (i < pattern.length) {
30
+ const [piece, next] = globToken(pattern, i);
31
+ re += piece;
32
+ i = next;
33
+ }
34
+ return new RegExp(`^${re}$`);
35
+ }
36
+
37
+ /** The regex source for the token at `i`, and the index after it. */
38
+ function globToken(pattern: string, i: number): readonly [string, number] {
39
+ const c = pattern.charAt(i);
40
+ if (c === '*' && pattern.charAt(i + 1) === '*') {
41
+ // `**/` may match zero segments; a trailing `**` matches anything.
42
+ return pattern.charAt(i + 2) === '/' ? ['(?:.*/)?', i + 3] : ['.*', i + 2];
43
+ }
44
+ if (c === '*') return ['[^/]*', i + 1];
45
+ if (c === '?') return ['[^/]', i + 1];
46
+ const close = c === '{' ? pattern.indexOf('}', i) : -1;
47
+ if (close !== -1) {
48
+ const options = pattern
49
+ .slice(i + 1, close)
50
+ .split(',')
51
+ .map(escapeRegex);
52
+ return [`(?:${options.join('|')})`, close + 1];
53
+ }
54
+ return [escapeRegex(c), i + 1];
55
+ }
56
+
57
+ function escapeRegex(s: string): string {
58
+ return s.replace(/[.+^$()|[\]\\{}]/g, '\\$&');
59
+ }
60
+
61
+ /**
62
+ * Directory prefix before the first wildcard segment, `''` for the pack
63
+ * root. A pattern without wildcards names a file; its prefix is that
64
+ * file's directory.
65
+ */
66
+ export function globStaticPrefix(pattern: string): string {
67
+ const segments = pattern.split('/');
68
+ const firstGlob = segments.findIndex((s) => GLOB_CHARS.test(s));
69
+ const dirSegments = firstGlob === -1 ? segments.slice(0, -1) : segments.slice(0, firstGlob);
70
+ return dirSegments.filter((s) => s !== '' && s !== '.').join('/');
71
+ }
72
+
73
+ /**
74
+ * The directories to walk or watch for a set of patterns: each pattern's
75
+ * static prefix, with any prefix inside another one dropped. `['']`
76
+ * means the pack root.
77
+ */
78
+ export function discoveryRoots(patterns: readonly string[]): readonly string[] {
79
+ const prefixes = [...new Set(patterns.map(globStaticPrefix))].sort();
80
+ return prefixes.filter(
81
+ (p) => !prefixes.some((other) => other !== p && (other === '' || p.startsWith(`${other}/`))),
82
+ );
83
+ }
84
+
85
+ /** `(relPath) => true` when the path matches any pattern and is not a test file. */
86
+ export function createGlobMatcher(patterns: readonly string[]): (relPath: string) => boolean {
87
+ const regexes = patterns.map(globToRegex);
88
+ return (relPath) => !TEST_FILE_REGEX.test(relPath) && regexes.some((r) => r.test(relPath));
89
+ }
@@ -0,0 +1,23 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ // Copyright (C) 2026 Kindgi Inc.
3
+
4
+ import { realpathSync } from 'node:fs';
5
+ import { pathToFileURL } from 'node:url';
6
+
7
+ /**
8
+ * Whether the module at `moduleUrl` is the process entrypoint (`node <file>`).
9
+ *
10
+ * Node resolves symlinks for the main module's `import.meta.url` but leaves
11
+ * `process.argv[1]` as given, so the two are compared through the real path:
12
+ * a package started through `node_modules/@kindgi/…` (a pnpm symlink) still
13
+ * runs its `main()`.
14
+ */
15
+ export function isProcessEntrypoint(moduleUrl: string): boolean {
16
+ const entry = process.argv[1];
17
+ if (entry === undefined) return false;
18
+ try {
19
+ return moduleUrl === pathToFileURL(realpathSync(entry)).href;
20
+ } catch {
21
+ return false;
22
+ }
23
+ }