@x-otto/cli 0.0.1-alpha.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 (100) hide show
  1. package/README.md +120 -0
  2. package/bin/otto.js +61 -0
  3. package/dist/auth-command-CXAB3_hs.js +11 -0
  4. package/dist/auth-command-CXAB3_hs.js.map +1 -0
  5. package/dist/build-id-BX4QHzM0.js +3 -0
  6. package/dist/build-id-BX4QHzM0.js.map +1 -0
  7. package/dist/bundled-extensions/plugin-otto-wire-protocols/otto-plugin.json +8 -0
  8. package/dist/bundled-extensions/plugin-otto-wire-protocols/plugin-dist/meta.json +1 -0
  9. package/dist/bundled-extensions/plugin-otto-wire-protocols/plugin-dist/plugin.cjs +60023 -0
  10. package/dist/bundled-extensions/plugin-otto-wire-protocols/plugin.ts +64 -0
  11. package/dist/bundled-extensions/plugin-otto-wire-protocols/src/anthropic-messages.ts +916 -0
  12. package/dist/bundled-extensions/plugin-otto-wire-protocols/src/anthropic-usage.ts +81 -0
  13. package/dist/bundled-extensions/plugin-otto-wire-protocols/src/openai-completions.ts +158 -0
  14. package/dist/bundled-extensions/plugin-otto-wire-protocols/src/openai-responses.ts +575 -0
  15. package/dist/bundled-extensions/plugin-skill-inductor/otto-plugin.json +8 -0
  16. package/dist/bundled-extensions/plugin-skill-inductor/plugin-dist/meta.json +1 -0
  17. package/dist/bundled-extensions/plugin-skill-inductor/plugin-dist/plugin.cjs +52781 -0
  18. package/dist/bundled-extensions/plugin-skill-inductor/plugin.ts +77 -0
  19. package/dist/bundled-extensions/plugin-skill-inductor/src/candidate-detector.ts +210 -0
  20. package/dist/bundled-extensions/plugin-skill-inductor/src/candidate-digest.ts +146 -0
  21. package/dist/bundled-extensions/plugin-skill-inductor/src/candidate-store.ts +87 -0
  22. package/dist/bundled-extensions/plugin-skill-inductor/src/episode-store.ts +122 -0
  23. package/dist/bundled-extensions/plugin-skill-inductor/src/episode.ts +125 -0
  24. package/dist/bundled-extensions/plugin-skill-inductor/src/extraction-prompt.ts +97 -0
  25. package/dist/bundled-extensions/plugin-skill-inductor/src/inductor-service.ts +183 -0
  26. package/dist/bundled-extensions/plugin-skill-inductor/src/similarity.ts +58 -0
  27. package/dist/command-dispatcher-BgStVcCF.js +13 -0
  28. package/dist/command-dispatcher-BgStVcCF.js.map +1 -0
  29. package/dist/config-migration-DatflIxs.js +21 -0
  30. package/dist/config-migration-DatflIxs.js.map +1 -0
  31. package/dist/daemon-COESl-zU.js +21 -0
  32. package/dist/daemon-COESl-zU.js.map +1 -0
  33. package/dist/daemon-base-YOWgYYN7.js +3 -0
  34. package/dist/daemon-base-YOWgYYN7.js.map +1 -0
  35. package/dist/debug-CiosFwme.js +34 -0
  36. package/dist/debug-CiosFwme.js.map +1 -0
  37. package/dist/discover-with-bundled-CznLjkkw.js +3 -0
  38. package/dist/discover-with-bundled-CznLjkkw.js.map +1 -0
  39. package/dist/doctor-C84-hhEr.js +30 -0
  40. package/dist/doctor-C84-hhEr.js.map +1 -0
  41. package/dist/doctor-attach-_zeIJewx.js +42 -0
  42. package/dist/doctor-attach-_zeIJewx.js.map +1 -0
  43. package/dist/doctor-cdp-K_mIFRfA.js +5 -0
  44. package/dist/doctor-cdp-K_mIFRfA.js.map +1 -0
  45. package/dist/doctor-heap-Rr2nNvcw.js +11 -0
  46. package/dist/doctor-heap-Rr2nNvcw.js.map +1 -0
  47. package/dist/doctor-ps-OL3FGQYu.js +8 -0
  48. package/dist/doctor-ps-OL3FGQYu.js.map +1 -0
  49. package/dist/extension-Bhzoel6s.js +169 -0
  50. package/dist/extension-Bhzoel6s.js.map +1 -0
  51. package/dist/extension-plugin-CnF6O_zK.js +7 -0
  52. package/dist/extension-plugin-CnF6O_zK.js.map +1 -0
  53. package/dist/extension-plugin-DWcakwRx.js +2 -0
  54. package/dist/extension-v-liRnUX.js +2 -0
  55. package/dist/feedback-BES_6ebi.js +21 -0
  56. package/dist/feedback-BES_6ebi.js.map +1 -0
  57. package/dist/feedback-BRbLUn9u.js +2 -0
  58. package/dist/feishu-setup-D6vnuNLw.js +6 -0
  59. package/dist/feishu-setup-D6vnuNLw.js.map +1 -0
  60. package/dist/index.d.ts +6 -0
  61. package/dist/index.d.ts.map +1 -0
  62. package/dist/index.js +20 -0
  63. package/dist/index.js.map +1 -0
  64. package/dist/install-DlVJRHRT.js +11 -0
  65. package/dist/install-DlVJRHRT.js.map +1 -0
  66. package/dist/install-WCcQ9k9M.js +2 -0
  67. package/dist/mcp-DbTYT7hB.js +12 -0
  68. package/dist/mcp-DbTYT7hB.js.map +1 -0
  69. package/dist/migrate-boDXhEyV.js +14 -0
  70. package/dist/migrate-boDXhEyV.js.map +1 -0
  71. package/dist/model-auth-C27iG6Jq.js +3 -0
  72. package/dist/model-auth-C27iG6Jq.js.map +1 -0
  73. package/dist/model-auth-C4bsWvvn.js +2 -0
  74. package/dist/observe-CVg-MRM2.js +32 -0
  75. package/dist/observe-CVg-MRM2.js.map +1 -0
  76. package/dist/parse-cli-tFDOeVU1.js +3 -0
  77. package/dist/parse-cli-tFDOeVU1.js.map +1 -0
  78. package/dist/persistenced-CCwHu1Ww.js +36 -0
  79. package/dist/persistenced-CCwHu1Ww.js.map +1 -0
  80. package/dist/proxy-BvdBsB1O.js +2 -0
  81. package/dist/proxy-evg7ZvoD.js +3 -0
  82. package/dist/proxy-evg7ZvoD.js.map +1 -0
  83. package/dist/ps-DKrluEuy.js +9 -0
  84. package/dist/ps-DKrluEuy.js.map +1 -0
  85. package/dist/registry-B3nfN62q.js +2 -0
  86. package/dist/registry-D0NONjLv.js +6 -0
  87. package/dist/registry-D0NONjLv.js.map +1 -0
  88. package/dist/remote-persistence-server-B_hONoVR.js +35 -0
  89. package/dist/remote-persistence-server-B_hONoVR.js.map +1 -0
  90. package/dist/run-attach-Ci5jYyCa.js +3 -0
  91. package/dist/run-attach-Ci5jYyCa.js.map +1 -0
  92. package/dist/serve-pVR6UIT9.js +21 -0
  93. package/dist/serve-pVR6UIT9.js.map +1 -0
  94. package/dist/service-client-BLNeugM9.js +3 -0
  95. package/dist/service-client-BLNeugM9.js.map +1 -0
  96. package/dist/time-travel-ZozuPPI-.js +21 -0
  97. package/dist/time-travel-ZozuPPI-.js.map +1 -0
  98. package/dist/version-C0qM-qJM.js +3 -0
  99. package/dist/version-C0qM-qJM.js.map +1 -0
  100. package/package.json +61 -0
@@ -0,0 +1,81 @@
1
+ /**
2
+ * provider-usage.ts — 供应商 usage 快照解析(状态栏「周/小时额度」展示,非错误路径)。
3
+ *
4
+ * 与 stream-errors.ts 的 extractAnthropicRateLimit 不同:那个函数只在**错误对象**(429 响应)
5
+ * 上解析 anthropic-ratelimit-unified-* 头族,用于判定是否硬额度耗尽。本文件解析**成功响应**的
6
+ * 同一头族——Anthropic SDK 的 `MessageStream.response` 在流正常结束后仍可访问底层 Response。
7
+ *
8
+ * 头名字经业界终端 Agent 实现反编译源码交叉验证(src/services/claudeAiLimits.ts):
9
+ * - 状态/类型/重置时间:`anthropic-ratelimit-unified-status`、`unified-representative-claim`、
10
+ * `unified-reset`——三字段有双源证据(SDK source map 反编译 + Anthropic 官方 Rate Limits 文档)
11
+ * - 各 bucket 的使用率:`anthropic-ratelimit-unified-{5h|7d}-utilization`(0-1 小数,需归一化
12
+ * 为百分比)。此前猜测的三个候选名(unified-utilization/remaining-percentage/used-percentage)
13
+ * 均为伪名——导致已用百分比在线上永远为 undefined,本次修复(2026-07-04 终局 review 发现)。
14
+ *
15
+ * ProviderUsageSnapshot 类型定义在 @x-otto/interchange(零依赖协议叶子),本文件只做解析实现。
16
+ */
17
+ import { headerGet } from '@x-otto/shared'
18
+ import type { ProviderUsageSnapshot } from '@x-otto/interchange'
19
+
20
+ /** limitType → bucket 缩写(用于拼接 `anthropic-ratelimit-unified-{abbr}-utilization` 头名) */
21
+ const LIMIT_TYPE_ABBREVIATION: Record<string, string> = {
22
+ five_hour: '5h',
23
+ seven_day: '7d',
24
+ seven_day_opus: '7d',
25
+ seven_day_sonnet: '7d',
26
+ seven_day_overage_included: '7d',
27
+ }
28
+
29
+ /**
30
+ * 从 per-bucket utilization 头读取已用百分比(0-1 小数,归一化为 0-100 整数)。
31
+ * 头名格式 `anthropic-ratelimit-unified-{abbr}-utilization`,abbr 由 limitType 映射。
32
+ * 若 limitType 未知或对应头不存在 → undefined(不编造,不猜测)。
33
+ */
34
+ function readBucketUtilization(
35
+ headers: Headers | Record<string, string>,
36
+ limitType: string | undefined,
37
+ ): number | undefined {
38
+ if (!limitType) return undefined
39
+ const abbr = LIMIT_TYPE_ABBREVIATION[limitType]
40
+ if (!abbr) return undefined
41
+ const raw = headerGet(headers, `anthropic-ratelimit-unified-${abbr}-utilization`)
42
+ if (raw === undefined) return undefined
43
+ const n = Number(raw)
44
+ if (!Number.isFinite(n)) return undefined
45
+ return Math.round(n * 100) // 0-1 → 0-100
46
+ }
47
+
48
+ /**
49
+ * 从成功响应的 Headers 中解析供应商 usage 快照。
50
+ * 仅在 `anthropic-ratelimit-unified-status` 存在时返回非 null(无该头族 = 非 Anthropic OAuth
51
+ * 订阅计划的请求,如 API key 走标准 RPM/TPM 限流,没有 unified 头族——调用方据此天然实现
52
+ * "API key 不显示",见 packages/ai/src/auth-store.ts getAuthSource)。
53
+ */
54
+ export function extractProviderUsageFromHeaders(
55
+ headers: Headers | Record<string, string> | null | undefined,
56
+ ): ProviderUsageSnapshot | null {
57
+ if (!headers) return null
58
+ const status = headerGet(headers, 'anthropic-ratelimit-unified-status')
59
+ if (status !== 'allowed' && status !== 'allowed_warning' && status !== 'rejected') return null
60
+
61
+ const limitType = headerGet(headers, 'anthropic-ratelimit-unified-representative-claim')
62
+ const resetRaw = headerGet(headers, 'anthropic-ratelimit-unified-reset')
63
+
64
+ // Overage(额外用量池)——仅 Anthropic OAuth 订阅计划带有此头族
65
+ const overageStatus = headerGet(headers, 'anthropic-ratelimit-unified-overage-status')
66
+ const overageResetRaw = headerGet(headers, 'anthropic-ratelimit-unified-overage-reset')
67
+ const overageDisabledReason = headerGet(
68
+ headers,
69
+ 'anthropic-ratelimit-unified-overage-disabled-reason',
70
+ )
71
+
72
+ return {
73
+ status,
74
+ limitType: limitType as ProviderUsageSnapshot['limitType'],
75
+ resetsAt: resetRaw ? Number(resetRaw) : undefined,
76
+ usedPercent: readBucketUtilization(headers, limitType),
77
+ overageStatus: overageStatus as ProviderUsageSnapshot['overageStatus'],
78
+ overageResetsAt: overageResetRaw ? Number(overageResetRaw) : undefined,
79
+ overageDisabledReason,
80
+ }
81
+ }
@@ -0,0 +1,158 @@
1
+ import { randomUUID } from 'node:crypto'
2
+
3
+ import { ProviderError } from '@x-otto/shared'
4
+ import {
5
+ buildChatCompletionsBody,
6
+ buildRequestHeaders,
7
+ registeredToolNames,
8
+ streamChatCompletions,
9
+ } from '@x-otto/provider'
10
+
11
+ import type {
12
+ Model,
13
+ ResolvedAuth,
14
+ StreamContext,
15
+ StreamEvent,
16
+ StreamOptions,
17
+ ProviderStream,
18
+ TokenDerivedHeader,
19
+ WireProtocolLogger,
20
+ WireRequestCustomization,
21
+ WireRequestCustomizeInput,
22
+ } from '@x-otto/provider'
23
+
24
+ /** RFC-185 D3:日志走宿主注入端口,缺省 no-op——插件源码禁 createLogger(防第二单例直写 stderr)。 */
25
+ const noopLogger: WireProtocolLogger = { debug: () => {}, info: () => {}, warn: () => {}, error: () => {} }
26
+
27
+ /**
28
+ * 通用 OpenAI Chat Completions provider(对标 opencode/pi 的 openai-compatible 基座)。
29
+ *
30
+ * 服务一切 Chat Completions 兼容的厂商(DeepSeek、未来的 OpenAI/groq/openrouter 等):
31
+ * - baseURL 取自 `model.baseUrl`(`${baseUrl}/chat/completions`)
32
+ * - 凭据按 `model.provider` 解析(注入的 resolveKey 接收 provider 名)
33
+ * - Bearer 鉴权,SSE 流式(含 reasoning_content → thinking)
34
+ *
35
+ * SSE 解析循环统一走 streamChatCompletions(RFC-074 R-AICHAT),本文件只负责凭据/url/body。
36
+ * Copilot 走自己的 provider(带 copilot 专用头);本 provider 不绑定任何厂商。
37
+ *
38
+ * RFC-122 D3:支持声明式 `tokenDerivedHeaders`(插件 provider 轴),JWT claim 值提取后注入请求头。
39
+ */
40
+ export type OpenAICompletionsCredentialResolver = (provider: string) => Promise<string | undefined>
41
+
42
+ // RFC-122 D3:TokenDerivedHeader 定义单一真源在 @x-otto/provider(协议中立层,见该包
43
+ // types.ts 头注释)——此前本文件与 openai-responses.ts 各自重复定义一份相同形状,
44
+ // 终局 review 收敛为直接 import,避免两份定义静默漂移。
45
+
46
+ export interface OpenAICompletionsProviderOptions {
47
+ resolveKey?: OpenAICompletionsCredentialResolver
48
+ /** RFC-122 D3:静态额外 headers(如 vendor 自定义头)。 */
49
+ headers?: Record<string, string>
50
+ /** RFC-122 D3:JWT claim 派生 headers。 */
51
+ tokenDerivedHeaders?: TokenDerivedHeader[]
52
+ /**
53
+ * RFC-270 D2:程序化请求定制点(拦截器)。此前只有 anthropic-messages 消费该钩子,
54
+ * 本协议补齐——返回的 `defaultHeaders` 并入头管线第 ④ 档。`transformSystemBlocks`
55
+ * 不适用于本协议(system 表达形态不同),按 `WireRequestCustomization` 契约忽略。
56
+ */
57
+ customizeRequest?: (input: WireRequestCustomizeInput) => WireRequestCustomization
58
+ /** RFC-270 D3:manifest 显式声明后,允许自定义头覆盖鉴权头(企业网关换鉴权)。 */
59
+ allowAuthHeaderOverride?: boolean
60
+ /** RFC-185 D3:宿主注入的日志端口,缺省 no-op。 */
61
+ logger?: WireProtocolLogger
62
+ }
63
+
64
+ class OpenAICompletionsStream implements ProviderStream {
65
+ readonly id = 'openai-completions'
66
+ readonly displayName = 'OpenAI Chat Completions'
67
+
68
+ /**
69
+ * 进程内稳定的会话 id——供 `customizeRequest` 定制器使用(对齐 anthropic-messages
70
+ * 的同名字段,RFC-270 D2 终局 review F2:此前本协议无 sessionId,钩子入参无法构造)。
71
+ */
72
+ private readonly requestSessionId = randomUUID()
73
+
74
+ constructor(
75
+ private readonly resolveCredential?: OpenAICompletionsCredentialResolver,
76
+ private readonly extraHeaders?: Record<string, string>,
77
+ private readonly tokenDerivedHeaders?: TokenDerivedHeader[],
78
+ private readonly logger: WireProtocolLogger = noopLogger,
79
+ /** 见 `OpenAICompletionsProviderOptions.customizeRequest`。缺省 = 零定制。 */
80
+ private readonly customizeRequest?: (input: WireRequestCustomizeInput) => WireRequestCustomization,
81
+ private readonly allowAuthHeaderOverride?: boolean,
82
+ ) {}
83
+
84
+ /** 统一鉴权解析(RFC-140 D5,`ProviderStream.resolveAuth`)——恒 api_key 模式。 */
85
+ async resolveAuth(model: Model): Promise<ResolvedAuth> {
86
+ if (this.resolveCredential) {
87
+ const key = await this.resolveCredential(model.provider)
88
+ if (key) {
89
+ return { token: key, mode: 'api_key' }
90
+ }
91
+ }
92
+ throw new ProviderError(
93
+ `Missing credential for provider '${model.provider}' (openai-completions).`,
94
+ { code: 'PROVIDER_AUTH_MISSING' },
95
+ )
96
+ }
97
+
98
+ async *converse(
99
+ model: Model,
100
+ context: StreamContext,
101
+ options: StreamOptions,
102
+ signal: AbortSignal,
103
+ ): AsyncIterable<StreamEvent> {
104
+ const auth = await this.resolveAuth(model)
105
+ const key = auth.token
106
+
107
+ this.logger.debug({ model: model.id, provider: model.provider }, 'Starting Chat Completions stream')
108
+
109
+ // RFC-270 D1:头组装收敛到 @x-otto/provider 的统一管线(此前本文件的合并顺序是
110
+ // `{ authorization, ...extraHeaders }`——自定义头覆盖鉴权头,与 openai-responses.ts
111
+ // 恰好相反且其注释谎称一致;管线统一为鉴权头 write-last 不可被覆盖)。
112
+ const customization = this.customizeRequest?.({
113
+ auth: { mode: auth.mode, beta: auth.beta },
114
+ sessionId: this.requestSessionId,
115
+ })
116
+
117
+ const headers = buildRequestHeaders({
118
+ staticHeaders: this.extraHeaders,
119
+ tokenDerived: this.tokenDerivedHeaders?.length
120
+ ? { headers: this.tokenDerivedHeaders, token: key }
121
+ : undefined,
122
+ customized: customization?.defaultHeaders,
123
+ auth: { authorization: `Bearer ${key}` },
124
+ allowAuthHeaderOverride: this.allowAuthHeaderOverride,
125
+ logger: this.logger,
126
+ diagnosticLabel: `openai-completions:${model.provider}`,
127
+ })
128
+
129
+ yield* streamChatCompletions({
130
+ url: `${model.baseUrl}/chat/completions`,
131
+ headers,
132
+ body: buildChatCompletionsBody(model, context),
133
+ identity: { api: model.api, provider: model.provider, model: model.id },
134
+ signal,
135
+ timeout: options.timeout,
136
+ // RFC-XX reasoning-thinking:reasoning 模型的 thinking 阶段可能长达分钟级零
137
+ // visible token——中间无任何 SSE chunk,默认 120s idle 会无差别单边坑所有 reasoning
138
+ // 模型(实测 turn 3 触发:thinking_start → 零输出 → stream.retry×2 → 崩)。
139
+ // 此处给 reasoning 模型显式加大 idle 苟(300s),非 reasoning 模型保持默认 120s。
140
+ idleTimeout: model.reasoning ? 300_000 : undefined,
141
+ // RFC-232(终局修正):跨 wire 协议救回违约响应,见 @x-otto/provider stream-state.ts。
142
+ registeredToolNames: registeredToolNames(context.tools),
143
+ })
144
+ }
145
+ }
146
+
147
+ export function createOpenAICompletionsProvider(
148
+ options: OpenAICompletionsProviderOptions = {},
149
+ ): ProviderStream {
150
+ return new OpenAICompletionsStream(
151
+ options.resolveKey,
152
+ options.headers,
153
+ options.tokenDerivedHeaders,
154
+ options.logger,
155
+ options.customizeRequest,
156
+ options.allowAuthHeaderOverride,
157
+ )
158
+ }