@ottocode/sdk 0.1.173

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 (125) hide show
  1. package/README.md +338 -0
  2. package/package.json +128 -0
  3. package/src/agent/types.ts +19 -0
  4. package/src/auth/src/copilot-oauth.ts +190 -0
  5. package/src/auth/src/index.ts +100 -0
  6. package/src/auth/src/oauth.ts +234 -0
  7. package/src/auth/src/openai-oauth.ts +394 -0
  8. package/src/auth/src/wallet.ts +51 -0
  9. package/src/browser.ts +32 -0
  10. package/src/config/src/index.ts +110 -0
  11. package/src/config/src/manager.ts +181 -0
  12. package/src/config/src/paths.ts +98 -0
  13. package/src/core/src/errors.ts +102 -0
  14. package/src/core/src/index.ts +108 -0
  15. package/src/core/src/providers/resolver.ts +244 -0
  16. package/src/core/src/streaming/artifacts.ts +41 -0
  17. package/src/core/src/terminals/bun-pty.ts +13 -0
  18. package/src/core/src/terminals/circular-buffer.ts +30 -0
  19. package/src/core/src/terminals/ensure-bun-pty.ts +70 -0
  20. package/src/core/src/terminals/index.ts +8 -0
  21. package/src/core/src/terminals/manager.ts +158 -0
  22. package/src/core/src/terminals/rust-libs.ts +30 -0
  23. package/src/core/src/terminals/terminal.ts +132 -0
  24. package/src/core/src/tools/bin-manager.ts +250 -0
  25. package/src/core/src/tools/builtin/bash.ts +155 -0
  26. package/src/core/src/tools/builtin/bash.txt +7 -0
  27. package/src/core/src/tools/builtin/file-cache.ts +39 -0
  28. package/src/core/src/tools/builtin/finish.ts +12 -0
  29. package/src/core/src/tools/builtin/finish.txt +10 -0
  30. package/src/core/src/tools/builtin/fs/cd.ts +19 -0
  31. package/src/core/src/tools/builtin/fs/cd.txt +5 -0
  32. package/src/core/src/tools/builtin/fs/index.ts +20 -0
  33. package/src/core/src/tools/builtin/fs/ls.ts +72 -0
  34. package/src/core/src/tools/builtin/fs/ls.txt +8 -0
  35. package/src/core/src/tools/builtin/fs/pwd.ts +17 -0
  36. package/src/core/src/tools/builtin/fs/pwd.txt +5 -0
  37. package/src/core/src/tools/builtin/fs/read.ts +119 -0
  38. package/src/core/src/tools/builtin/fs/read.txt +8 -0
  39. package/src/core/src/tools/builtin/fs/tree.ts +149 -0
  40. package/src/core/src/tools/builtin/fs/tree.txt +11 -0
  41. package/src/core/src/tools/builtin/fs/util.ts +95 -0
  42. package/src/core/src/tools/builtin/fs/write.ts +106 -0
  43. package/src/core/src/tools/builtin/fs/write.txt +11 -0
  44. package/src/core/src/tools/builtin/git.commit.txt +6 -0
  45. package/src/core/src/tools/builtin/git.diff.txt +5 -0
  46. package/src/core/src/tools/builtin/git.status.txt +5 -0
  47. package/src/core/src/tools/builtin/git.ts +151 -0
  48. package/src/core/src/tools/builtin/glob.ts +128 -0
  49. package/src/core/src/tools/builtin/glob.txt +10 -0
  50. package/src/core/src/tools/builtin/grep.ts +136 -0
  51. package/src/core/src/tools/builtin/grep.txt +9 -0
  52. package/src/core/src/tools/builtin/ignore.ts +45 -0
  53. package/src/core/src/tools/builtin/patch/apply.ts +546 -0
  54. package/src/core/src/tools/builtin/patch/constants.ts +5 -0
  55. package/src/core/src/tools/builtin/patch/normalize.ts +31 -0
  56. package/src/core/src/tools/builtin/patch/parse-enveloped.ts +209 -0
  57. package/src/core/src/tools/builtin/patch/parse-unified.ts +231 -0
  58. package/src/core/src/tools/builtin/patch/parse.ts +28 -0
  59. package/src/core/src/tools/builtin/patch/text.ts +23 -0
  60. package/src/core/src/tools/builtin/patch/types.ts +82 -0
  61. package/src/core/src/tools/builtin/patch.ts +167 -0
  62. package/src/core/src/tools/builtin/patch.txt +207 -0
  63. package/src/core/src/tools/builtin/progress.ts +55 -0
  64. package/src/core/src/tools/builtin/progress.txt +7 -0
  65. package/src/core/src/tools/builtin/ripgrep.ts +125 -0
  66. package/src/core/src/tools/builtin/ripgrep.txt +7 -0
  67. package/src/core/src/tools/builtin/terminal.ts +300 -0
  68. package/src/core/src/tools/builtin/terminal.txt +93 -0
  69. package/src/core/src/tools/builtin/todos.ts +66 -0
  70. package/src/core/src/tools/builtin/todos.txt +7 -0
  71. package/src/core/src/tools/builtin/websearch.ts +250 -0
  72. package/src/core/src/tools/builtin/websearch.txt +12 -0
  73. package/src/core/src/tools/error.ts +67 -0
  74. package/src/core/src/tools/loader.ts +421 -0
  75. package/src/core/src/types/index.ts +11 -0
  76. package/src/core/src/types/types.ts +4 -0
  77. package/src/core/src/utils/ansi.ts +27 -0
  78. package/src/core/src/utils/debug.ts +40 -0
  79. package/src/core/src/utils/logger.ts +150 -0
  80. package/src/index.ts +313 -0
  81. package/src/prompts/src/agents/build.txt +89 -0
  82. package/src/prompts/src/agents/general.txt +15 -0
  83. package/src/prompts/src/agents/plan.txt +10 -0
  84. package/src/prompts/src/agents/research.txt +50 -0
  85. package/src/prompts/src/base.txt +24 -0
  86. package/src/prompts/src/debug.ts +104 -0
  87. package/src/prompts/src/index.ts +1 -0
  88. package/src/prompts/src/modes/oneshot.txt +9 -0
  89. package/src/prompts/src/providers/anthropic.txt +247 -0
  90. package/src/prompts/src/providers/anthropicSpoof.txt +1 -0
  91. package/src/prompts/src/providers/default.txt +466 -0
  92. package/src/prompts/src/providers/google.txt +230 -0
  93. package/src/prompts/src/providers/moonshot.txt +24 -0
  94. package/src/prompts/src/providers/openai.txt +414 -0
  95. package/src/prompts/src/providers.ts +143 -0
  96. package/src/providers/src/anthropic-caching.ts +202 -0
  97. package/src/providers/src/anthropic-oauth-client.ts +157 -0
  98. package/src/providers/src/authorization.ts +17 -0
  99. package/src/providers/src/catalog-manual.ts +135 -0
  100. package/src/providers/src/catalog-merged.ts +9 -0
  101. package/src/providers/src/catalog.ts +8329 -0
  102. package/src/providers/src/copilot-client.ts +39 -0
  103. package/src/providers/src/env.ts +31 -0
  104. package/src/providers/src/google-client.ts +16 -0
  105. package/src/providers/src/index.ts +75 -0
  106. package/src/providers/src/moonshot-client.ts +25 -0
  107. package/src/providers/src/oauth-models.ts +39 -0
  108. package/src/providers/src/openai-oauth-client.ts +108 -0
  109. package/src/providers/src/opencode-client.ts +64 -0
  110. package/src/providers/src/openrouter-client.ts +31 -0
  111. package/src/providers/src/pricing.ts +178 -0
  112. package/src/providers/src/setu-client.ts +643 -0
  113. package/src/providers/src/utils.ts +210 -0
  114. package/src/providers/src/validate.ts +39 -0
  115. package/src/providers/src/zai-client.ts +47 -0
  116. package/src/skills/index.ts +34 -0
  117. package/src/skills/loader.ts +152 -0
  118. package/src/skills/parser.ts +108 -0
  119. package/src/skills/tool.ts +87 -0
  120. package/src/skills/types.ts +41 -0
  121. package/src/skills/validator.ts +110 -0
  122. package/src/types/src/auth.ts +33 -0
  123. package/src/types/src/config.ts +36 -0
  124. package/src/types/src/index.ts +20 -0
  125. package/src/types/src/provider.ts +71 -0
package/README.md ADDED
@@ -0,0 +1,338 @@
1
+ # @ottocode/sdk
2
+
3
+ > **The single source of truth for ottocode functionality** - Comprehensive, tree-shakable, and developer-friendly.
4
+
5
+ ## Overview
6
+
7
+ `@ottocode/sdk` is the unified SDK for building AI agents with ottocode. All authentication, configuration, providers, prompts, tools, and core AI functionality are included in this single package.
8
+
9
+ **Why use the SDK?**
10
+ - ✅ **Single import**: All functionality from one package
11
+ - ✅ **Tree-shakable**: Bundlers only include what you use
12
+ - ✅ **Type-safe**: Full TypeScript support with comprehensive types
13
+ - ✅ **Zero circular dependencies**: Clean architecture
14
+ - ✅ **Consistent API**: No need to remember which module exports what
15
+
16
+ ## Installation
17
+
18
+ ```bash
19
+ bun add @ottocode/sdk
20
+ ```
21
+
22
+ ## Quick Start
23
+
24
+ ```typescript
25
+ import { generateText, resolveModel } from '@ottocode/sdk';
26
+ import type { ProviderId } from '@ottocode/sdk';
27
+
28
+ const model = resolveModel('anthropic', 'claude-sonnet-4-20250514');
29
+
30
+ const { text } = await generateText({
31
+ model,
32
+ prompt: 'What is the meaning of life?',
33
+ });
34
+
35
+ console.log(text);
36
+ ```
37
+
38
+ ## What's Included?
39
+
40
+ ### Types
41
+
42
+ All shared types are available:
43
+
44
+ ```typescript
45
+ import type {
46
+ ProviderId,
47
+ ModelInfo,
48
+ AuthInfo,
49
+ OttoConfig,
50
+ ProviderConfig,
51
+ Scope
52
+ } from '@ottocode/sdk';
53
+ ```
54
+
55
+ ### Providers
56
+
57
+ Provider catalog and utilities:
58
+
59
+ ```typescript
60
+ import {
61
+ catalog,
62
+ isProviderId,
63
+ providerIds,
64
+ defaultModelFor,
65
+ hasModel,
66
+ isProviderAuthorized,
67
+ validateProviderModel,
68
+ estimateModelCostUsd,
69
+ providerEnvVar,
70
+ readEnvKey,
71
+ setEnvKey
72
+ } from '@ottocode/sdk';
73
+
74
+ // Check available providers
75
+ console.log(providerIds); // ['openai', 'anthropic', 'google', 'openrouter', 'opencode', 'setu']
76
+
77
+ // Get model information
78
+ const models = catalog.anthropic.models;
79
+
80
+ // Validate provider/model combination
81
+ const result = validateProviderModel('anthropic', 'claude-sonnet-4-20250514');
82
+ ```
83
+
84
+ ### Authentication
85
+
86
+ Manage API keys and OAuth:
87
+
88
+ ```typescript
89
+ import {
90
+ getAuth,
91
+ setAuth,
92
+ removeAuth,
93
+ getAllAuth,
94
+ authorize,
95
+ createApiKey
96
+ } from '@ottocode/sdk';
97
+
98
+ // Set API key
99
+ await setAuth('openai', { apiKey: 'sk-...' });
100
+
101
+ // Get auth info
102
+ const auth = await getAuth('openai');
103
+
104
+ // OAuth flow
105
+ const url = await authorize('anthropic');
106
+ console.log(`Visit: ${url}`);
107
+ ```
108
+
109
+ ### Configuration
110
+
111
+ Load and manage configuration:
112
+
113
+ ```typescript
114
+ import { loadConfig } from '@ottocode/sdk';
115
+ import type { OttoConfig } from '@ottocode/sdk';
116
+
117
+ const config = await loadConfig();
118
+ console.log(config.provider); // 'anthropic'
119
+ console.log(config.model); // 'claude-sonnet-4-20250514'
120
+ ```
121
+
122
+ ### Prompts
123
+
124
+ Pre-built system prompts:
125
+
126
+ ```typescript
127
+ import { providerBasePrompt } from '@ottocode/sdk';
128
+
129
+ const prompt = providerBasePrompt('anthropic');
130
+ ```
131
+
132
+ ### Core AI Functions
133
+
134
+ AI SDK re-exports and utilities:
135
+
136
+ ```typescript
137
+ import {
138
+ generateText,
139
+ streamText,
140
+ generateObject,
141
+ streamObject,
142
+ tool,
143
+ resolveModel,
144
+ discoverProjectTools,
145
+ buildFsTools,
146
+ buildGitTools,
147
+ createFileDiffArtifact,
148
+ z
149
+ } from '@ottocode/sdk';
150
+ import type { CoreMessage, Tool, DiscoveredTool } from '@ottocode/sdk';
151
+
152
+ // Generate text
153
+ const { text } = await generateText({
154
+ model: resolveModel('anthropic'),
155
+ prompt: 'Hello!',
156
+ });
157
+
158
+ // Stream text with tools
159
+ const { textStream } = streamText({
160
+ model: resolveModel('openai'),
161
+ prompt: 'What files are in the current directory?',
162
+ tools: buildFsTools(),
163
+ });
164
+
165
+ // Generate structured output
166
+ const { object } = await generateObject({
167
+ model: resolveModel('anthropic'),
168
+ schema: z.object({
169
+ name: z.string(),
170
+ age: z.number(),
171
+ }),
172
+ prompt: 'Generate a person',
173
+ });
174
+
175
+ // Discover project tools
176
+ const tools = await discoverProjectTools('/path/to/project');
177
+ ```
178
+
179
+ ### Error Handling
180
+
181
+ Typed error classes:
182
+
183
+ ```typescript
184
+ import {
185
+ OttoError,
186
+ AuthError,
187
+ ConfigError,
188
+ ToolError,
189
+ ProviderError,
190
+ DatabaseError,
191
+ ValidationError,
192
+ NotFoundError,
193
+ ServiceError
194
+ } from '@ottocode/sdk';
195
+
196
+ try {
197
+ // ... your code
198
+ } catch (error) {
199
+ if (error instanceof AuthError) {
200
+ console.error('Authentication failed:', error.message);
201
+ }
202
+ }
203
+ ```
204
+
205
+ ## Tree-Shaking
206
+
207
+ The SDK is fully tree-shakable. Modern bundlers (Vite, Rollup, esbuild, webpack) will only include the code you actually use:
208
+
209
+ ```typescript
210
+ // Only includes generateText and resolveModel code
211
+ import { generateText, resolveModel } from '@ottocode/sdk';
212
+ ```
213
+
214
+ ## Architecture
215
+
216
+ The SDK contains all functionality internally:
217
+
218
+ ```
219
+ @ottocode/sdk/src/
220
+ ├── auth/ ← Authentication (OAuth, API keys)
221
+ ├── config/ ← Configuration (global + project)
222
+ ├── core/ ← Core AI functionality
223
+ │ ├── providers/ (model resolution)
224
+ │ ├── tools/ (builtin tools)
225
+ │ ├── streaming/ (artifacts)
226
+ │ └── errors.ts (error classes)
227
+ ├── prompts/ ← System prompts
228
+ ├── providers/ ← Provider catalog & utilities
229
+ └── index.ts ← Main exports
230
+ ```
231
+
232
+ Related packages:
233
+ - `@ottocode/database` - SQLite persistence (depends on sdk)
234
+ - `@ottocode/server` - HTTP API (depends on sdk, database)
235
+ - `@ottocode/api` - Type-safe API client (standalone)
236
+ - `@ottocode/web-sdk` - React hooks & components (depends on api)
237
+
238
+ ## Examples
239
+
240
+ ### Basic Agent
241
+
242
+ ```typescript
243
+ import { generateText, resolveModel } from '@ottocode/sdk';
244
+
245
+ const model = resolveModel('anthropic');
246
+
247
+ const { text } = await generateText({
248
+ model,
249
+ prompt: 'Explain TypeScript generics',
250
+ });
251
+
252
+ console.log(text);
253
+ ```
254
+
255
+ ### Agent with Tools
256
+
257
+ ```typescript
258
+ import { streamText, resolveModel, buildFsTools, buildGitTools } from '@ottocode/sdk';
259
+
260
+ const tools = {
261
+ ...buildFsTools(),
262
+ ...buildGitTools(),
263
+ };
264
+
265
+ const { textStream } = streamText({
266
+ model: resolveModel('openai'),
267
+ prompt: 'What Git branch am I on? List the files in the current directory.',
268
+ tools,
269
+ });
270
+
271
+ for await (const chunk of textStream) {
272
+ process.stdout.write(chunk);
273
+ }
274
+ ```
275
+
276
+ ### Structured Output
277
+
278
+ ```typescript
279
+ import { generateObject, resolveModel, z } from '@ottocode/sdk';
280
+
281
+ const schema = z.object({
282
+ sentiment: z.enum(['positive', 'negative', 'neutral']),
283
+ confidence: z.number().min(0).max(1),
284
+ keywords: z.array(z.string()),
285
+ });
286
+
287
+ const { object } = await generateObject({
288
+ model: resolveModel('anthropic'),
289
+ schema,
290
+ prompt: 'Analyze: "This SDK is amazing!"',
291
+ });
292
+
293
+ console.log(object);
294
+ // { sentiment: 'positive', confidence: 0.95, keywords: ['amazing', 'SDK'] }
295
+ ```
296
+
297
+ ### With Configuration
298
+
299
+ ```typescript
300
+ import { generateText, resolveModel, loadConfig } from '@ottocode/sdk';
301
+
302
+ const config = await loadConfig();
303
+ const model = resolveModel(config.provider, config.model);
304
+
305
+ const { text } = await generateText({
306
+ model,
307
+ prompt: 'Hello from ottocode!',
308
+ temperature: config.temperature,
309
+ });
310
+ ```
311
+
312
+ ## TypeScript
313
+
314
+ Full TypeScript support with comprehensive types:
315
+
316
+ ```typescript
317
+ import type {
318
+ ProviderId,
319
+ ModelInfo,
320
+ OttoConfig,
321
+ CoreMessage,
322
+ Tool,
323
+ DiscoveredTool,
324
+ Artifact
325
+ } from '@ottocode/sdk';
326
+
327
+ // All types are fully documented and type-safe
328
+ const providerId: ProviderId = 'anthropic';
329
+ const config: OttoConfig = await loadConfig();
330
+ ```
331
+
332
+ ## License
333
+
334
+ MIT
335
+
336
+ ## Contributing
337
+
338
+ See the main repository for contribution guidelines.
package/package.json ADDED
@@ -0,0 +1,128 @@
1
+ {
2
+ "name": "@ottocode/sdk",
3
+ "version": "0.1.173",
4
+ "description": "AI agent SDK for building intelligent assistants - tree-shakable and comprehensive",
5
+ "author": "nitishxyz",
6
+ "license": "MIT",
7
+ "homepage": "https://github.com/nitishxyz/otto#readme",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/nitishxyz/otto.git",
11
+ "directory": "packages/sdk"
12
+ },
13
+ "bugs": {
14
+ "url": "https://github.com/nitishxyz/otto/issues"
15
+ },
16
+ "type": "module",
17
+ "main": "./src/index.ts",
18
+ "types": "./src/index.ts",
19
+ "exports": {
20
+ ".": {
21
+ "import": "./src/index.ts",
22
+ "types": "./src/index.ts"
23
+ },
24
+ "./browser": {
25
+ "import": "./src/browser.ts",
26
+ "types": "./src/browser.ts"
27
+ },
28
+ "./tools/builtin/fs": {
29
+ "import": "./src/core/src/tools/builtin/fs/index.ts",
30
+ "types": "./src/core/src/tools/builtin/fs/index.ts"
31
+ },
32
+ "./tools/builtin/git": {
33
+ "import": "./src/core/src/tools/builtin/git.ts",
34
+ "types": "./src/core/src/tools/builtin/git.ts"
35
+ },
36
+ "./tools/builtin/bash": {
37
+ "import": "./src/core/src/tools/builtin/bash.ts",
38
+ "types": "./src/core/src/tools/builtin/bash.ts"
39
+ },
40
+ "./tools/builtin/edit": {
41
+ "import": "./src/core/src/tools/builtin/edit.ts",
42
+ "types": "./src/core/src/tools/builtin/edit.ts"
43
+ },
44
+ "./tools/builtin/finish": {
45
+ "import": "./src/core/src/tools/builtin/finish.ts",
46
+ "types": "./src/core/src/tools/builtin/finish.ts"
47
+ },
48
+ "./tools/builtin/grep": {
49
+ "import": "./src/core/src/tools/builtin/grep.ts",
50
+ "types": "./src/core/src/tools/builtin/grep.ts"
51
+ },
52
+ "./tools/builtin/patch": {
53
+ "import": "./src/core/src/tools/builtin/patch.ts",
54
+ "types": "./src/core/src/tools/builtin/patch.ts"
55
+ },
56
+ "./tools/builtin/plan": {
57
+ "import": "./src/core/src/tools/builtin/plan.ts",
58
+ "types": "./src/core/src/tools/builtin/plan.ts"
59
+ },
60
+ "./tools/builtin/progress": {
61
+ "import": "./src/core/src/tools/builtin/progress.ts",
62
+ "types": "./src/core/src/tools/builtin/progress.ts"
63
+ },
64
+ "./tools/builtin/ripgrep": {
65
+ "import": "./src/core/src/tools/builtin/ripgrep.ts",
66
+ "types": "./src/core/src/tools/builtin/ripgrep.ts"
67
+ },
68
+ "./tools/builtin/websearch": {
69
+ "import": "./src/core/src/tools/builtin/websearch.ts",
70
+ "types": "./src/core/src/tools/builtin/websearch.ts"
71
+ },
72
+ "./tools/builtin/terminal": {
73
+ "import": "./src/core/src/tools/builtin/terminal.ts",
74
+ "types": "./src/core/src/tools/builtin/terminal.ts"
75
+ },
76
+ "./tools/error": {
77
+ "import": "./src/core/src/tools/error.ts",
78
+ "types": "./src/core/src/tools/error.ts"
79
+ },
80
+ "./prompts/*": "./src/prompts/src/*"
81
+ },
82
+ "files": [
83
+ "src",
84
+ "README.md",
85
+ "LICENSE"
86
+ ],
87
+ "scripts": {
88
+ "dev": "bun run src/index.ts",
89
+ "test": "bun test",
90
+ "typecheck": "tsc --noEmit"
91
+ },
92
+ "dependencies": {
93
+ "@ai-sdk/anthropic": "^3.0.0",
94
+ "@ai-sdk/google": "^3.0.0",
95
+ "@ai-sdk/openai": "^3.0.0",
96
+ "@ai-sdk/openai-compatible": "^2.0.0",
97
+ "@openauthjs/openauth": "^0.4.3",
98
+ "@openrouter/ai-sdk-provider": "^1.2.0",
99
+ "@solana/web3.js": "^1.95.2",
100
+ "ai": "^6.0.0",
101
+ "bs58": "^6.0.0",
102
+ "bun-pty": "^0.3.2",
103
+ "diff": "^8.0.2",
104
+ "fast-glob": "^3.3.2",
105
+ "hono": "^4.9.9",
106
+ "opencode-anthropic-auth": "^0.0.2",
107
+ "tweetnacl": "^1.0.3",
108
+ "x402": "^1.1.0",
109
+ "zod": "^4.1.8"
110
+ },
111
+ "devDependencies": {
112
+ "@types/bun": "latest",
113
+ "typescript": "^5"
114
+ },
115
+ "keywords": [
116
+ "ai",
117
+ "sdk",
118
+ "agents",
119
+ "tools",
120
+ "llm",
121
+ "anthropic",
122
+ "openai",
123
+ "development",
124
+ "assistant",
125
+ "tree-shakable",
126
+ "typescript"
127
+ ]
128
+ }
@@ -0,0 +1,19 @@
1
+ import type { ProviderName } from '../core/src/index.ts';
2
+
3
+ export type AgentConfig = {
4
+ name: string;
5
+ prompt: string;
6
+ tools: string[];
7
+ provider?: ProviderName;
8
+ model?: string;
9
+ };
10
+
11
+ export type AgentConfigEntry = {
12
+ tools?: string[];
13
+ appendTools?: string[];
14
+ prompt?: string;
15
+ provider?: string;
16
+ model?: string;
17
+ };
18
+
19
+ export type AgentsJson = Record<string, AgentConfigEntry>;
@@ -0,0 +1,190 @@
1
+ import { spawn } from 'node:child_process';
2
+
3
+ const CLIENT_ID = 'Ov23lip6QjVYxHUAeW4d';
4
+ const POLLING_SAFETY_MARGIN_MS = 3000;
5
+
6
+ const DEVICE_CODE_URL = 'https://github.com/login/device/code';
7
+ const ACCESS_TOKEN_URL = 'https://github.com/login/oauth/access_token';
8
+
9
+ export type CopilotDeviceCodeResponse = {
10
+ verification_uri: string;
11
+ user_code: string;
12
+ device_code: string;
13
+ interval: number;
14
+ };
15
+
16
+ export type CopilotOAuthResult = {
17
+ access_token: string;
18
+ };
19
+
20
+ async function openBrowser(url: string) {
21
+ const platform = process.platform;
22
+ let command: string;
23
+ switch (platform) {
24
+ case 'darwin':
25
+ command = `open "${url}"`;
26
+ break;
27
+ case 'win32':
28
+ command = `start "${url}"`;
29
+ break;
30
+ default:
31
+ command = `xdg-open "${url}"`;
32
+ break;
33
+ }
34
+ return new Promise<void>((resolve, reject) => {
35
+ const child = spawn(command, [], { shell: true });
36
+ child.on('error', reject);
37
+ child.on('exit', (code) => {
38
+ if (code === 0) resolve();
39
+ else reject(new Error(`Failed to open browser (exit code ${code})`));
40
+ });
41
+ });
42
+ }
43
+
44
+ export async function requestDeviceCode(): Promise<CopilotDeviceCodeResponse> {
45
+ const response = await fetch(DEVICE_CODE_URL, {
46
+ method: 'POST',
47
+ headers: {
48
+ Accept: 'application/json',
49
+ 'Content-Type': 'application/json',
50
+ },
51
+ body: JSON.stringify({
52
+ client_id: CLIENT_ID,
53
+ scope: 'read:user',
54
+ }),
55
+ });
56
+
57
+ if (!response.ok) {
58
+ throw new Error('Failed to initiate GitHub device authorization');
59
+ }
60
+
61
+ return (await response.json()) as CopilotDeviceCodeResponse;
62
+ }
63
+
64
+ export async function pollForToken(
65
+ deviceCode: string,
66
+ interval: number,
67
+ ): Promise<string> {
68
+ while (true) {
69
+ const response = await fetch(ACCESS_TOKEN_URL, {
70
+ method: 'POST',
71
+ headers: {
72
+ Accept: 'application/json',
73
+ 'Content-Type': 'application/json',
74
+ },
75
+ body: JSON.stringify({
76
+ client_id: CLIENT_ID,
77
+ device_code: deviceCode,
78
+ grant_type: 'urn:ietf:params:oauth:grant-type:device_code',
79
+ }),
80
+ });
81
+
82
+ if (!response.ok) {
83
+ throw new Error('Token exchange request failed');
84
+ }
85
+
86
+ const data = (await response.json()) as {
87
+ access_token?: string;
88
+ error?: string;
89
+ interval?: number;
90
+ };
91
+
92
+ if (data.access_token) {
93
+ return data.access_token;
94
+ }
95
+
96
+ if (data.error === 'authorization_pending') {
97
+ await Bun.sleep(interval * 1000 + POLLING_SAFETY_MARGIN_MS);
98
+ continue;
99
+ }
100
+
101
+ if (data.error === 'slow_down') {
102
+ let newInterval = (interval + 5) * 1000;
103
+ const serverInterval = data.interval;
104
+ if (
105
+ serverInterval &&
106
+ typeof serverInterval === 'number' &&
107
+ serverInterval > 0
108
+ ) {
109
+ newInterval = serverInterval * 1000;
110
+ }
111
+ await Bun.sleep(newInterval + POLLING_SAFETY_MARGIN_MS);
112
+ continue;
113
+ }
114
+
115
+ if (data.error) {
116
+ throw new Error(`GitHub OAuth error: ${data.error}`);
117
+ }
118
+
119
+ await Bun.sleep(interval * 1000 + POLLING_SAFETY_MARGIN_MS);
120
+ }
121
+ }
122
+
123
+ export async function authorizeCopilot(): Promise<{
124
+ verificationUri: string;
125
+ userCode: string;
126
+ deviceCode: string;
127
+ interval: number;
128
+ }> {
129
+ const deviceData = await requestDeviceCode();
130
+ return {
131
+ verificationUri: deviceData.verification_uri,
132
+ userCode: deviceData.user_code,
133
+ deviceCode: deviceData.device_code,
134
+ interval: deviceData.interval,
135
+ };
136
+ }
137
+
138
+ export type CopilotPollResult =
139
+ | { status: 'complete'; accessToken: string }
140
+ | { status: 'pending' }
141
+ | { status: 'error'; error: string };
142
+
143
+ export async function pollForTokenOnce(
144
+ deviceCode: string,
145
+ ): Promise<CopilotPollResult> {
146
+ const response = await fetch(ACCESS_TOKEN_URL, {
147
+ method: 'POST',
148
+ headers: {
149
+ Accept: 'application/json',
150
+ 'Content-Type': 'application/json',
151
+ },
152
+ body: JSON.stringify({
153
+ client_id: CLIENT_ID,
154
+ device_code: deviceCode,
155
+ grant_type: 'urn:ietf:params:oauth:grant-type:device_code',
156
+ }),
157
+ });
158
+
159
+ if (!response.ok) {
160
+ return { status: 'error', error: 'Token exchange request failed' };
161
+ }
162
+
163
+ const data = (await response.json()) as {
164
+ access_token?: string;
165
+ error?: string;
166
+ };
167
+
168
+ if (data.access_token) {
169
+ return { status: 'complete', accessToken: data.access_token };
170
+ }
171
+
172
+ if (data.error === 'authorization_pending' || data.error === 'slow_down') {
173
+ return { status: 'pending' };
174
+ }
175
+
176
+ if (data.error) {
177
+ return { status: 'error', error: `GitHub OAuth error: ${data.error}` };
178
+ }
179
+
180
+ return { status: 'pending' };
181
+ }
182
+
183
+ export async function openCopilotAuthUrl(url: string): Promise<boolean> {
184
+ try {
185
+ await openBrowser(url);
186
+ return true;
187
+ } catch {
188
+ return false;
189
+ }
190
+ }