@factory/droid-sdk 0.6.0 → 0.8.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 (92) hide show
  1. package/README.md +35 -482
  2. package/dist/chunk-4G2RKM2H.js +25831 -0
  3. package/dist/chunk-4G2RKM2H.js.map +1 -0
  4. package/dist/chunk-ROFAQUG4.mjs +25831 -0
  5. package/dist/chunk-ROFAQUG4.mjs.map +1 -0
  6. package/dist/{index-CwXX5IUH.d.cts → index-_nUPGHpH.d.mts} +92630 -79659
  7. package/dist/{index-CwXX5IUH.d.ts → index-_nUPGHpH.d.ts} +92630 -79659
  8. package/dist/index.d.mts +4 -0
  9. package/dist/index.d.ts +4 -93103
  10. package/dist/index.js +829 -11745
  11. package/dist/index.js.map +1 -1
  12. package/dist/index.mjs +830 -0
  13. package/dist/index.mjs.map +1 -0
  14. package/dist/node.d.mts +757 -0
  15. package/dist/node.d.ts +757 -0
  16. package/dist/node.js +4731 -0
  17. package/dist/node.js.map +1 -0
  18. package/dist/node.mjs +4731 -0
  19. package/dist/node.mjs.map +1 -0
  20. package/docs/typescript-sdk-reference.md +1437 -0
  21. package/examples/browser/daemon-lifecycle.ts +95 -0
  22. package/examples/browser/daemon-model-discovery.ts +47 -0
  23. package/examples/browser/daemon-multi-session.ts +66 -0
  24. package/examples/browser/daemon-session.ts +73 -0
  25. package/examples/browser/index.html +69 -0
  26. package/examples/browser/list-sessions.ts +50 -0
  27. package/examples/node/abort-session-stream.ts +34 -0
  28. package/examples/node/ask-user-elicitation.ts +39 -0
  29. package/examples/node/compact-session.ts +63 -0
  30. package/examples/node/custom-system-prompt.ts +35 -0
  31. package/examples/node/document-input.ts +58 -0
  32. package/examples/node/enter-spec-mode.ts +87 -0
  33. package/examples/node/external-mcp.ts +45 -0
  34. package/examples/node/fork-session.ts +56 -0
  35. package/examples/node/hook-execution.ts +94 -0
  36. package/examples/node/image-attachment.ts +41 -0
  37. package/examples/node/init-metadata.ts +24 -0
  38. package/examples/node/interrupt-session.ts +55 -0
  39. package/examples/node/list-sessions.ts +20 -0
  40. package/examples/node/model-discovery.ts +28 -0
  41. package/examples/node/model-selection.ts +35 -0
  42. package/examples/node/multi-turn-session.ts +37 -0
  43. package/examples/node/observability.ts +75 -0
  44. package/examples/node/partial-stream.ts +56 -0
  45. package/examples/node/permission-handler.ts +60 -0
  46. package/examples/node/rename-session.ts +33 -0
  47. package/examples/node/result-metadata.ts +43 -0
  48. package/examples/node/resume-session.ts +41 -0
  49. package/examples/node/rewind-session.ts +79 -0
  50. package/examples/node/run.ts +32 -0
  51. package/examples/node/sdk-mcp-tool.ts +56 -0
  52. package/examples/node/session-settings.ts +35 -0
  53. package/examples/node/session-stream.ts +59 -0
  54. package/examples/node/skills.ts +24 -0
  55. package/examples/node/spec-mode-new-session.ts +120 -0
  56. package/examples/node/spec-mode-same-session.ts +119 -0
  57. package/examples/node/structured-output.ts +96 -0
  58. package/examples/node/tool-controls.ts +26 -0
  59. package/examples/package.json +3 -0
  60. package/package.json +20 -42
  61. package/dist/index.cjs +0 -12135
  62. package/dist/index.cjs.map +0 -1
  63. package/dist/index.d.cts +0 -93103
  64. package/dist/protocol/index.cjs +0 -6466
  65. package/dist/protocol/index.cjs.map +0 -1
  66. package/dist/protocol/index.d.cts +0 -2
  67. package/dist/protocol/index.d.ts +0 -2
  68. package/dist/protocol/index.js +0 -6078
  69. package/dist/protocol/index.js.map +0 -1
  70. package/docs/daemon-usage-guide.md +0 -544
  71. package/docs/sdk-usage-guide.md +0 -748
  72. package/examples/abort-session-stream.ts +0 -34
  73. package/examples/daemon-multi-session.ts +0 -67
  74. package/examples/droid-dev-structured-output.ts +0 -44
  75. package/examples/fork-session.ts +0 -68
  76. package/examples/hook-execution.ts +0 -68
  77. package/examples/init-metadata.ts +0 -24
  78. package/examples/interrupt-session.ts +0 -35
  79. package/examples/list-sessions.ts +0 -19
  80. package/examples/multi-turn-session.ts +0 -40
  81. package/examples/permission-handler.ts +0 -41
  82. package/examples/readme-structured-output.ts +0 -26
  83. package/examples/result-metadata.ts +0 -63
  84. package/examples/run.ts +0 -39
  85. package/examples/sdk-mcp-tool.ts +0 -44
  86. package/examples/session-stream.ts +0 -57
  87. package/examples/spec-mode-new-session.ts +0 -61
  88. package/examples/spec-mode-same-session.ts +0 -72
  89. package/examples/structured-output.ts +0 -60
  90. package/examples/test-compact.ts +0 -65
  91. package/examples/test-rewind.ts +0 -87
  92. package/examples/tool-controls.ts +0 -37
@@ -0,0 +1,119 @@
1
+ /**
2
+ * Spec mode: approve and implement in the same session.
3
+ *
4
+ * Approving the spec with `ProceedOnce` keeps implementation in the session
5
+ * that planned it. The example prints its session id and reports that no
6
+ * handoff occurred, which is what distinguishes its output from
7
+ * spec-mode-new-session.ts.
8
+ *
9
+ * A handoff would announce itself on the notification envelope: `params.sessionId`
10
+ * on incoming notifications would stop matching `session.id`. The typed message
11
+ * stream carries no session id per message, so the example subscribes with
12
+ * `session.onNotification()` and validates each envelope with
13
+ * `SessionNotificationSchema`.
14
+ *
15
+ * Usage:
16
+ * npx tsx examples/node/spec-mode-same-session.ts
17
+ */
18
+
19
+ import { mkdtemp, readFile, rm } from 'node:fs/promises';
20
+ import { tmpdir } from 'node:os';
21
+ import { join } from 'node:path';
22
+
23
+ import {
24
+ type ClientPermissionHandler,
25
+ DroidInteractionMode,
26
+ DroidMessageType,
27
+ ReasoningEffort,
28
+ SessionNotificationSchema,
29
+ ToolConfirmationOutcome,
30
+ ToolConfirmationType,
31
+ createSession,
32
+ } from '@factory/droid-sdk/node';
33
+
34
+ const tempDir = await mkdtemp(join(tmpdir(), 'droid-sdk-spec-'));
35
+ const outputPath = join(tempDir, 'hello.txt');
36
+
37
+ let handoffSessionId: string | null = null;
38
+
39
+ const handlePermissionRequest: ClientPermissionHandler = ({
40
+ toolUses,
41
+ options,
42
+ }) => {
43
+ const exitsSpecMode =
44
+ toolUses.length > 0 &&
45
+ toolUses.every(
46
+ (toolUse) => toolUse.details.type === ToolConfirmationType.ExitSpecMode
47
+ );
48
+ const createsExpectedFile =
49
+ toolUses.length > 0 &&
50
+ toolUses.every(
51
+ (toolUse) =>
52
+ toolUse.details.type === ToolConfirmationType.Create &&
53
+ toolUse.details.filePath === outputPath
54
+ );
55
+
56
+ const outcome =
57
+ exitsSpecMode || createsExpectedFile
58
+ ? ToolConfirmationOutcome.ProceedOnce
59
+ : ToolConfirmationOutcome.Cancel;
60
+ if (!options.some((option) => option.value === outcome)) {
61
+ throw new Error(`Outcome not offered: ${outcome}`);
62
+ }
63
+ return outcome;
64
+ };
65
+
66
+ try {
67
+ const session = await createSession({
68
+ interactionMode: DroidInteractionMode.Spec,
69
+ specModeReasoningEffort: ReasoningEffort.High,
70
+ permissionHandler: handlePermissionRequest,
71
+ });
72
+
73
+ // The typed message stream carries no per-message session id, and the
74
+ // precise handoff signal the SDK uses internally is not part of the public
75
+ // surface, so a foreign envelope session id is the available proxy. It is
76
+ // only a proxy: any notification scoped to another session also trips it.
77
+ const unsubscribe = session.onNotification((notification) => {
78
+ const parsed = SessionNotificationSchema.safeParse(notification);
79
+ if (!parsed.success) return;
80
+ const envelopeSessionId = parsed.data.params.sessionId;
81
+ if (envelopeSessionId !== undefined && envelopeSessionId !== session.id) {
82
+ handoffSessionId = envelopeSessionId;
83
+ }
84
+ });
85
+
86
+ try {
87
+ for await (const message of session.stream(
88
+ `Plan then create ${outputPath} containing "Hello from Droid".`
89
+ )) {
90
+ if (message.type === DroidMessageType.Result && !message.success) {
91
+ throw new Error(
92
+ message.error?.message ?? `Turn ended: ${message.subtype}`
93
+ );
94
+ }
95
+ }
96
+ } finally {
97
+ unsubscribe();
98
+ await session.close();
99
+ }
100
+
101
+ console.log(`planning session: ${session.id}`);
102
+ console.log(
103
+ `implementing session: ${handoffSessionId ?? '<same session>'}`
104
+ );
105
+ console.log(await readFile(outputPath, 'utf8'));
106
+ } finally {
107
+ await rm(tempDir, { recursive: true, force: true });
108
+ }
109
+
110
+ // A foreign session id is only a proxy for a handoff, so warn rather than
111
+ // fail: it may mean `ProceedOnce` handed implementation off, or merely that
112
+ // some unrelated session-scoped notification arrived during the run.
113
+ if (handoffSessionId !== null) {
114
+ console.warn(
115
+ `Observed a notification scoped to a different session (${String(handoffSessionId)}). ` +
116
+ 'That may mean implementation was handed off, or that unrelated ' +
117
+ 'session-scoped activity occurred during this run.'
118
+ );
119
+ }
@@ -0,0 +1,96 @@
1
+ /**
2
+ * Structured output example.
3
+ *
4
+ * Passes a JSON schema via `outputFormat` and reads the parsed object back
5
+ * from `result.structuredOutput`.
6
+ *
7
+ * Usage:
8
+ * npx tsx examples/node/structured-output.ts
9
+ */
10
+
11
+ import { OutputFormatType, run } from '@factory/droid-sdk/node';
12
+
13
+ interface CodeReview {
14
+ summary: string;
15
+ findings: Array<{
16
+ line: number;
17
+ severity: 'low' | 'medium' | 'high';
18
+ message: string;
19
+ }>;
20
+ }
21
+
22
+ function isCodeReview(value: unknown): value is CodeReview {
23
+ if (
24
+ value === null ||
25
+ typeof value !== 'object' ||
26
+ !('summary' in value) ||
27
+ typeof value.summary !== 'string' ||
28
+ !('findings' in value) ||
29
+ !Array.isArray(value.findings)
30
+ ) {
31
+ return false;
32
+ }
33
+
34
+ return value.findings.every(
35
+ (finding) =>
36
+ finding !== null &&
37
+ typeof finding === 'object' &&
38
+ 'line' in finding &&
39
+ typeof finding.line === 'number' &&
40
+ 'severity' in finding &&
41
+ ['low', 'medium', 'high'].includes(String(finding.severity)) &&
42
+ 'message' in finding &&
43
+ typeof finding.message === 'string'
44
+ );
45
+ }
46
+
47
+ const result = await run(
48
+ `Review this code for correctness issues:
49
+
50
+ function getDisplayName(user) {
51
+ return user.profile.name.trim();
52
+ }`,
53
+ {
54
+ outputFormat: {
55
+ type: OutputFormatType.JsonSchema,
56
+ schema: {
57
+ type: 'object',
58
+ properties: {
59
+ summary: { type: 'string' },
60
+ findings: {
61
+ type: 'array',
62
+ items: {
63
+ type: 'object',
64
+ properties: {
65
+ line: { type: 'number' },
66
+ severity: {
67
+ type: 'string',
68
+ enum: ['low', 'medium', 'high'],
69
+ },
70
+ message: { type: 'string' },
71
+ },
72
+ required: ['line', 'severity', 'message'],
73
+ },
74
+ },
75
+ },
76
+ required: ['summary', 'findings'],
77
+ additionalProperties: false,
78
+ },
79
+ },
80
+ }
81
+ );
82
+
83
+ if (!result.success) {
84
+ const detail =
85
+ result.structuredOutputError?.message ??
86
+ result.error?.message ??
87
+ 'Unknown error';
88
+ throw new Error(`run() failed (${result.subtype}): ${detail}`);
89
+ }
90
+
91
+ // The SDK returns `unknown`; validate it before reading fields.
92
+ if (!isCodeReview(result.structuredOutput)) {
93
+ throw new Error('run() returned an unexpected structured output shape.');
94
+ }
95
+
96
+ console.log(JSON.stringify(result.structuredOutput, null, 2));
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Tool controls example.
3
+ *
4
+ * Disables tools at session creation, then inspects availability.
5
+ *
6
+ * Usage:
7
+ * npx tsx examples/node/tool-controls.ts
8
+ */
9
+
10
+ import { createSession } from '@factory/droid-sdk/node';
11
+
12
+ const session = await createSession({
13
+ disabledToolIds: ['Execute'],
14
+ });
15
+
16
+ try {
17
+ const tools = await session.listTools();
18
+
19
+ for (const tool of tools) {
20
+ if (['Read', 'Grep', 'Execute'].includes(tool.id)) {
21
+ console.log(`${tool.id}: ${String(tool.allowed)}`);
22
+ }
23
+ }
24
+ } finally {
25
+ await session.close();
26
+ }
@@ -0,0 +1,3 @@
1
+ {
2
+ "type": "module"
3
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@factory/droid-sdk",
3
- "version": "0.6.0",
3
+ "version": "0.8.0",
4
4
  "private": false,
5
5
  "description": "TypeScript SDK for the Factory Droid CLI",
6
6
  "keywords": [
@@ -12,32 +12,34 @@
12
12
  "sdk"
13
13
  ],
14
14
  "license": "Apache-2.0",
15
- "type": "module",
16
- "main": "./dist/index.cjs",
17
- "module": "./dist/index.js",
15
+ "main": "./dist/index.js",
16
+ "module": "./dist/index.mjs",
18
17
  "types": "./dist/index.d.ts",
19
18
  "exports": {
20
19
  ".": {
21
20
  "import": {
22
- "types": "./dist/index.d.ts",
23
- "default": "./dist/index.js"
21
+ "types": "./dist/index.d.mts",
22
+ "default": "./dist/index.mjs"
24
23
  },
25
24
  "require": {
26
- "types": "./dist/index.d.cts",
27
- "default": "./dist/index.cjs"
25
+ "types": "./dist/index.d.ts",
26
+ "default": "./dist/index.js"
28
27
  }
29
28
  },
30
- "./protocol": {
29
+ "./node": {
31
30
  "import": {
32
- "types": "./dist/protocol/index.d.ts",
33
- "default": "./dist/protocol/index.js"
31
+ "types": "./dist/node.d.mts",
32
+ "default": "./dist/node.mjs"
34
33
  },
35
34
  "require": {
36
- "types": "./dist/protocol/index.d.cts",
37
- "default": "./dist/protocol/index.cjs"
35
+ "types": "./dist/node.d.ts",
36
+ "default": "./dist/node.js"
38
37
  }
39
38
  }
40
39
  },
40
+ "browser": {
41
+ "ws": false
42
+ },
41
43
  "files": [
42
44
  "dist",
43
45
  "docs",
@@ -45,18 +47,6 @@
45
47
  "README.md",
46
48
  "LICENSE"
47
49
  ],
48
- "scripts": {
49
- "build": "tsup",
50
- "prepack": "npm run build",
51
- "typecheck": "tsc --noEmit",
52
- "typecheck:examples": "tsc --noEmit -p tsconfig.examples.json",
53
- "test": "vitest run",
54
- "test:coverage": "vitest run --coverage",
55
- "lint": "eslint src/ tests/ examples/",
56
- "fix": "eslint --fix src/ tests/ examples/",
57
- "format": "prettier --write .",
58
- "format:check": "prettier --check ."
59
- },
60
50
  "repository": {
61
51
  "type": "git",
62
52
  "url": "git+https://github.com/Factory-AI/droid-sdk-typescript.git"
@@ -66,26 +56,14 @@
66
56
  },
67
57
  "dependencies": {
68
58
  "@modelcontextprotocol/sdk": "^1.29.0",
69
- "uuid": "^11.1.0",
59
+ "@opentelemetry/api": "^1.9.0",
60
+ "eventemitter3": "^5.0.1",
61
+ "fast-deep-equal": "^3.1.3",
62
+ "isows": "^1.0.7",
63
+ "uuid": "^9.0.0",
70
64
  "ws": "^8.21.0",
71
65
  "zod": "^3.24.0"
72
66
  },
73
- "devDependencies": {
74
- "@eslint/js": "^9.39.4",
75
- "@types/node": "^22.15.0",
76
- "@types/uuid": "^10.0.0",
77
- "@types/ws": "^8.18.1",
78
- "@vitest/coverage-v8": "^3.1.0",
79
- "eslint": "^9.24.0",
80
- "eslint-import-resolver-typescript": "^4.4.4",
81
- "eslint-plugin-import": "^2.32.0",
82
- "eslint-plugin-unused-imports": "^4.4.1",
83
- "prettier": "^3.8.1",
84
- "tsup": "^8.4.0",
85
- "typescript": "^5.8.0",
86
- "typescript-eslint": "^8.57.2",
87
- "vitest": "^3.1.0"
88
- },
89
67
  "homepage": "https://github.com/Factory-AI/droid-sdk-typescript#readme",
90
68
  "bugs": {
91
69
  "url": "https://github.com/Factory-AI/droid-sdk-typescript/issues"