opencode-qwen-cli-auth 1.0.3 → 1.0.4

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 (48) hide show
  1. package/dist/index.d.ts +20 -20
  2. package/dist/index.js +166 -166
  3. package/dist/lib/prompts/fallback/opencode-qwen-prompt.txt +109 -0
  4. package/dist/lib/prompts/opencode-qwen.d.ts +14 -0
  5. package/dist/lib/prompts/opencode-qwen.d.ts.map +1 -0
  6. package/dist/lib/prompts/opencode-qwen.js +121 -0
  7. package/dist/lib/prompts/opencode-qwen.js.map +1 -0
  8. package/dist/lib/prompts/qwen-code.d.ts +25 -0
  9. package/dist/lib/prompts/qwen-code.d.ts.map +1 -0
  10. package/dist/lib/prompts/qwen-code.js +307 -0
  11. package/dist/lib/prompts/qwen-code.js.map +1 -0
  12. package/dist/lib/prompts/qwen-opencode-bridge.d.ts +15 -0
  13. package/dist/lib/prompts/qwen-opencode-bridge.d.ts.map +1 -0
  14. package/dist/lib/prompts/qwen-opencode-bridge.js +81 -0
  15. package/dist/lib/prompts/qwen-opencode-bridge.js.map +1 -0
  16. package/dist/lib/request/fetch-helpers.d.ts +19 -0
  17. package/dist/lib/request/fetch-helpers.d.ts.map +1 -0
  18. package/dist/lib/request/fetch-helpers.js +50 -0
  19. package/dist/lib/request/fetch-helpers.js.map +1 -0
  20. package/dist/lib/request/header-utils.d.ts +38 -0
  21. package/dist/lib/request/header-utils.d.ts.map +1 -0
  22. package/dist/lib/request/header-utils.js +75 -0
  23. package/dist/lib/request/header-utils.js.map +1 -0
  24. package/dist/lib/request/openai-chunk-builder.d.ts +68 -0
  25. package/dist/lib/request/openai-chunk-builder.d.ts.map +1 -0
  26. package/dist/lib/request/openai-chunk-builder.js +110 -0
  27. package/dist/lib/request/openai-chunk-builder.js.map +1 -0
  28. package/dist/lib/request/payload-analyzer.d.ts +34 -0
  29. package/dist/lib/request/payload-analyzer.d.ts.map +1 -0
  30. package/dist/lib/request/payload-analyzer.js +114 -0
  31. package/dist/lib/request/payload-analyzer.js.map +1 -0
  32. package/dist/lib/request/request-transformer.d.ts +39 -0
  33. package/dist/lib/request/request-transformer.d.ts.map +1 -0
  34. package/dist/lib/request/request-transformer.js +108 -0
  35. package/dist/lib/request/request-transformer.js.map +1 -0
  36. package/dist/lib/request/response-handler.d.ts +15 -0
  37. package/dist/lib/request/response-handler.d.ts.map +1 -0
  38. package/dist/lib/request/response-handler.js +90 -0
  39. package/dist/lib/request/response-handler.js.map +1 -0
  40. package/dist/lib/request/sse-parser.d.ts +36 -0
  41. package/dist/lib/request/sse-parser.d.ts.map +1 -0
  42. package/dist/lib/request/sse-parser.js +85 -0
  43. package/dist/lib/request/sse-parser.js.map +1 -0
  44. package/dist/lib/request/stream-normalizer.d.ts +18 -0
  45. package/dist/lib/request/stream-normalizer.d.ts.map +1 -0
  46. package/dist/lib/request/stream-normalizer.js +140 -0
  47. package/dist/lib/request/stream-normalizer.js.map +1 -0
  48. package/package.json +66 -66
@@ -0,0 +1,90 @@
1
+ import { logRequest, LOGGING_ENABLED, logError } from "../logger.js";
2
+ // Re-export the new modular stream normalizer
3
+ export { normalizeSseToOpenAI } from './stream-normalizer.js';
4
+ /**
5
+ * Parse SSE stream to extract final response
6
+ * @param sseText - Complete SSE stream text
7
+ * @returns Final response object or null if not found
8
+ */
9
+ function parseSseStream(sseText) {
10
+ const lines = sseText.split('\n');
11
+ for (const line of lines) {
12
+ if (line.startsWith('data: ')) {
13
+ try {
14
+ const data = JSON.parse(line.substring(6));
15
+ // Look for response.done event with final data
16
+ if (data.type === 'response.done' || data.type === 'response.completed') {
17
+ return data.response;
18
+ }
19
+ }
20
+ catch (e) {
21
+ // Skip malformed JSON
22
+ }
23
+ }
24
+ }
25
+ return null;
26
+ }
27
+ /**
28
+ * Convert SSE stream response to JSON for generateText()
29
+ * @param response - Fetch response with SSE stream
30
+ * @param headers - Response headers
31
+ * @returns Response with JSON body
32
+ */
33
+ export async function convertSseToJson(response, headers) {
34
+ if (!response.body) {
35
+ throw new Error('[qwen-oauth-plugin] Response has no body');
36
+ }
37
+ const reader = response.body.getReader();
38
+ const decoder = new TextDecoder();
39
+ let fullText = '';
40
+ try {
41
+ // Consume the entire stream
42
+ while (true) {
43
+ const { done, value } = await reader.read();
44
+ if (done)
45
+ break;
46
+ fullText += decoder.decode(value, { stream: true });
47
+ }
48
+ if (LOGGING_ENABLED) {
49
+ logRequest("stream-full", { fullContent: fullText });
50
+ }
51
+ // Parse SSE events to extract the final response
52
+ const finalResponse = parseSseStream(fullText);
53
+ if (!finalResponse) {
54
+ logError('Could not find final response in SSE stream');
55
+ logRequest("stream-error", { error: "No response.done event found" });
56
+ // Return original stream if we can't parse
57
+ return new Response(fullText, {
58
+ status: response.status,
59
+ statusText: response.statusText,
60
+ headers: headers,
61
+ });
62
+ }
63
+ // Return as plain JSON (not SSE)
64
+ const jsonHeaders = new Headers(headers);
65
+ jsonHeaders.set('content-type', 'application/json; charset=utf-8');
66
+ return new Response(JSON.stringify(finalResponse), {
67
+ status: response.status,
68
+ statusText: response.statusText,
69
+ headers: jsonHeaders,
70
+ });
71
+ }
72
+ catch (error) {
73
+ logError('Error converting stream:', error);
74
+ logRequest("stream-error", { error: String(error) });
75
+ throw error;
76
+ }
77
+ }
78
+ /**
79
+ * Ensure response has content-type header
80
+ * @param headers - Response headers
81
+ * @returns Headers with content-type set
82
+ */
83
+ export function ensureContentType(headers) {
84
+ const responseHeaders = new Headers(headers);
85
+ if (!responseHeaders.has('content-type')) {
86
+ responseHeaders.set('content-type', 'text/event-stream; charset=utf-8');
87
+ }
88
+ return responseHeaders;
89
+ }
90
+ //# sourceMappingURL=response-handler.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"response-handler.js","sourceRoot":"","sources":["../../../lib/request/response-handler.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Server-Sent Events (SSE) parsing utilities
3
+ * Handles parsing of SSE streams into structured events
4
+ */
5
+ /**
6
+ * Represents a parsed SSE event
7
+ */
8
+ export interface SseEvent {
9
+ /** Event type (e.g., 'message', 'response.done') */
10
+ type?: string;
11
+ /** Event data (parsed JSON or raw string) */
12
+ data: unknown;
13
+ /** Raw data string before parsing */
14
+ rawData: string;
15
+ }
16
+ /**
17
+ * Parse a single SSE frame into events
18
+ * A frame is text between double newlines (\n\n)
19
+ *
20
+ * @param frame - SSE frame text
21
+ * @returns Array of parsed events
22
+ */
23
+ export declare function parseFrame(frame: string): SseEvent[];
24
+ /**
25
+ * Check if an event indicates stream completion
26
+ * @param event - SSE event to check
27
+ * @returns True if this is a completion event
28
+ */
29
+ export declare function isCompletionEvent(event: SseEvent): boolean;
30
+ /**
31
+ * Check if an event contains content data
32
+ * @param event - SSE event to check
33
+ * @returns True if this event has content
34
+ */
35
+ export declare function hasContent(event: SseEvent): boolean;
36
+ //# sourceMappingURL=sse-parser.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sse-parser.d.ts","sourceRoot":"","sources":["../../../lib/request/sse-parser.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH;;GAEG;AACH,MAAM,WAAW,QAAQ;IACxB,oDAAoD;IACpD,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,6CAA6C;IAC7C,IAAI,EAAE,OAAO,CAAC;IACd,qCAAqC;IACrC,OAAO,EAAE,MAAM,CAAC;CAChB;AAED;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,QAAQ,EAAE,CA6CpD;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,QAAQ,GAAG,OAAO,CAM1D;AAED;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,QAAQ,GAAG,OAAO,CAkBnD"}
@@ -0,0 +1,85 @@
1
+ /**
2
+ * Server-Sent Events (SSE) parsing utilities
3
+ * Handles parsing of SSE streams into structured events
4
+ */
5
+ /**
6
+ * Parse a single SSE frame into events
7
+ * A frame is text between double newlines (\n\n)
8
+ *
9
+ * @param frame - SSE frame text
10
+ * @returns Array of parsed events
11
+ */
12
+ export function parseFrame(frame) {
13
+ const events = [];
14
+ const lines = frame.split('\n');
15
+ for (const line of lines) {
16
+ const trimmed = line.trim();
17
+ if (!trimmed || !trimmed.startsWith('data:'))
18
+ continue;
19
+ const dataStr = trimmed.slice(5).trim(); // Remove 'data:' prefix
20
+ // Handle [DONE] marker
21
+ if (dataStr === '[DONE]') {
22
+ events.push({
23
+ type: 'done',
24
+ data: null,
25
+ rawData: dataStr,
26
+ });
27
+ continue;
28
+ }
29
+ // Try to parse as JSON
30
+ let payload = null;
31
+ try {
32
+ payload = JSON.parse(dataStr);
33
+ }
34
+ catch {
35
+ // Not JSON, treat as raw string
36
+ events.push({
37
+ type: 'raw',
38
+ data: dataStr,
39
+ rawData: dataStr,
40
+ });
41
+ continue;
42
+ }
43
+ // Extract type from payload if available
44
+ const payloadObj = payload;
45
+ const type = payloadObj?.type || 'message';
46
+ events.push({
47
+ type,
48
+ data: payload,
49
+ rawData: dataStr,
50
+ });
51
+ }
52
+ return events;
53
+ }
54
+ /**
55
+ * Check if an event indicates stream completion
56
+ * @param event - SSE event to check
57
+ * @returns True if this is a completion event
58
+ */
59
+ export function isCompletionEvent(event) {
60
+ return (event.type === 'done' ||
61
+ event.type === 'response.done' ||
62
+ event.type === 'response.completed');
63
+ }
64
+ /**
65
+ * Check if an event contains content data
66
+ * @param event - SSE event to check
67
+ * @returns True if this event has content
68
+ */
69
+ export function hasContent(event) {
70
+ if (!event.data || typeof event.data !== 'object') {
71
+ return false;
72
+ }
73
+ // Check various content field patterns with proper type guards
74
+ const payload = event.data;
75
+ return !!(payload.delta ||
76
+ payload.text ||
77
+ payload.output_text ||
78
+ (payload.message && typeof payload.message === 'object' && payload.message.content) ||
79
+ (Array.isArray(payload.choices) && payload.choices.length > 0 &&
80
+ typeof payload.choices[0] === 'object' &&
81
+ payload.choices[0].delta &&
82
+ typeof payload.choices[0].delta === 'object' &&
83
+ payload.choices[0].delta.content));
84
+ }
85
+ //# sourceMappingURL=sse-parser.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sse-parser.js","sourceRoot":"","sources":["../../../lib/request/sse-parser.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAcH;;;;;;GAMG;AACH,MAAM,UAAU,UAAU,CAAC,KAAa;IACvC,MAAM,MAAM,GAAe,EAAE,CAAC;IAC9B,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAEhC,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QAC1B,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;QAC5B,IAAI,CAAC,OAAO,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,OAAO,CAAC;YAAE,SAAS;QAEvD,MAAM,OAAO,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,wBAAwB;QAEjE,uBAAuB;QACvB,IAAI,OAAO,KAAK,QAAQ,EAAE,CAAC;YAC1B,MAAM,CAAC,IAAI,CAAC;gBACX,IAAI,EAAE,MAAM;gBACZ,IAAI,EAAE,IAAI;gBACV,OAAO,EAAE,OAAO;aAChB,CAAC,CAAC;YACH,SAAS;QACV,CAAC;QAED,uBAAuB;QACvB,IAAI,OAAO,GAAY,IAAI,CAAC;QAC5B,IAAI,CAAC;YACJ,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QAC/B,CAAC;QAAC,MAAM,CAAC;YACR,gCAAgC;YAChC,MAAM,CAAC,IAAI,CAAC;gBACX,IAAI,EAAE,KAAK;gBACX,IAAI,EAAE,OAAO;gBACb,OAAO,EAAE,OAAO;aAChB,CAAC,CAAC;YACH,SAAS;QACV,CAAC;QAED,yCAAyC;QACzC,MAAM,UAAU,GAAG,OAAkC,CAAC;QACtD,MAAM,IAAI,GAAI,UAAU,EAAE,IAAe,IAAI,SAAS,CAAC;QACvD,MAAM,CAAC,IAAI,CAAC;YACX,IAAI;YACJ,IAAI,EAAE,OAAO;YACb,OAAO,EAAE,OAAO;SAChB,CAAC,CAAC;IACJ,CAAC;IAED,OAAO,MAAM,CAAC;AACf,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,iBAAiB,CAAC,KAAe;IAChD,OAAO,CACN,KAAK,CAAC,IAAI,KAAK,MAAM;QACrB,KAAK,CAAC,IAAI,KAAK,eAAe;QAC9B,KAAK,CAAC,IAAI,KAAK,oBAAoB,CACnC,CAAC;AACH,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,UAAU,CAAC,KAAe;IACzC,IAAI,CAAC,KAAK,CAAC,IAAI,IAAI,OAAO,KAAK,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;QACnD,OAAO,KAAK,CAAC;IACd,CAAC;IAED,+DAA+D;IAC/D,MAAM,OAAO,GAAG,KAAK,CAAC,IAA+B,CAAC;IACtD,OAAO,CAAC,CAAC,CACR,OAAO,CAAC,KAAK;QACb,OAAO,CAAC,IAAI;QACZ,OAAO,CAAC,WAAW;QACnB,CAAC,OAAO,CAAC,OAAO,IAAI,OAAO,OAAO,CAAC,OAAO,KAAK,QAAQ,IAAK,OAAO,CAAC,OAAmC,CAAC,OAAO,CAAC;QAChH,CAAC,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,OAAO,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC;YAC5D,OAAO,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,QAAQ;YACrC,OAAO,CAAC,OAAO,CAAC,CAAC,CAA6B,CAAC,KAAK;YACrD,OAAQ,OAAO,CAAC,OAAO,CAAC,CAAC,CAA6B,CAAC,KAAK,KAAK,QAAQ;YACvE,OAAO,CAAC,OAAO,CAAC,CAAC,CAA6B,CAAC,KAAiC,CAAC,OAAO,CAAC,CAC5F,CAAC;AACH,CAAC"}
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Stream normalization for converting various SSE formats to OpenAI-compatible streams
3
+ */
4
+ /**
5
+ * Normalize Qwen Portal API SSE stream into OpenAI Chat Completions chunk stream
6
+ *
7
+ * This function handles multiple response formats:
8
+ * - Cumulative deltas (each chunk contains all previous text)
9
+ * - Incremental deltas (each chunk contains only new text)
10
+ * - Various payload structures (Qwen, OpenAI, etc.)
11
+ *
12
+ * If the format is not recognized, it passes through the original stream unchanged.
13
+ *
14
+ * @param response - Original SSE response
15
+ * @returns Normalized response with OpenAI-compatible chunks
16
+ */
17
+ export declare function normalizeSseToOpenAI(response: Response): Promise<Response>;
18
+ //# sourceMappingURL=stream-normalizer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stream-normalizer.d.ts","sourceRoot":"","sources":["../../../lib/request/stream-normalizer.ts"],"names":[],"mappings":"AAAA;;GAEG;AAmBH;;;;;;;;;;;;GAYG;AACH,wBAAsB,oBAAoB,CAAC,QAAQ,EAAE,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC,CAoIhF"}
@@ -0,0 +1,140 @@
1
+ /**
2
+ * Stream normalization for converting various SSE formats to OpenAI-compatible streams
3
+ */
4
+ import { parseFrame, isCompletionEvent } from './sse-parser.js';
5
+ import { extractText, calculateDelta } from './payload-analyzer.js';
6
+ import { OpenAIChunkBuilder } from './openai-chunk-builder.js';
7
+ import { STREAM_CONFIG } from '../constants.js';
8
+ import { logWarn } from '../logger.js';
9
+ /**
10
+ * Normalize Qwen Portal API SSE stream into OpenAI Chat Completions chunk stream
11
+ *
12
+ * This function handles multiple response formats:
13
+ * - Cumulative deltas (each chunk contains all previous text)
14
+ * - Incremental deltas (each chunk contains only new text)
15
+ * - Various payload structures (Qwen, OpenAI, etc.)
16
+ *
17
+ * If the format is not recognized, it passes through the original stream unchanged.
18
+ *
19
+ * @param response - Original SSE response
20
+ * @returns Normalized response with OpenAI-compatible chunks
21
+ */
22
+ export async function normalizeSseToOpenAI(response) {
23
+ if (!response.body) {
24
+ throw new Error('[qwen-oauth-plugin] Response has no body');
25
+ }
26
+ const encoder = new TextEncoder();
27
+ const decoder = new TextDecoder();
28
+ const reader = response.body.getReader();
29
+ const state = {
30
+ buffer: '',
31
+ cumulativeText: '',
32
+ recognized: false,
33
+ finished: false,
34
+ rawSseOut: '',
35
+ };
36
+ const builder = new OpenAIChunkBuilder('coder-model');
37
+ // Create a transformed stream
38
+ const stream = new ReadableStream({
39
+ async pull(controller) {
40
+ while (true) {
41
+ const { done, value } = await reader.read();
42
+ if (done)
43
+ break;
44
+ state.buffer += decoder.decode(value, { stream: true });
45
+ // Process complete SSE events (separated by double newlines)
46
+ let idx;
47
+ while ((idx = state.buffer.indexOf('\n\n')) !== -1) {
48
+ const frame = state.buffer.slice(0, idx);
49
+ state.buffer = state.buffer.slice(idx + 2);
50
+ const events = parseFrame(frame);
51
+ for (const event of events) {
52
+ // Handle completion events
53
+ if (isCompletionEvent(event)) {
54
+ if (!state.recognized) {
55
+ // Pass-through mode: emit raw data and [DONE]
56
+ state.rawSseOut += `data: ${event.rawData}\n\n`;
57
+ state.rawSseOut += builder.createDoneMarker();
58
+ controller.enqueue(encoder.encode(state.rawSseOut));
59
+ }
60
+ else {
61
+ // Normalized mode: emit finish chunk and [DONE]
62
+ const finishChunk = builder.createFinishChunk('stop');
63
+ controller.enqueue(encoder.encode(builder.formatAsSSE(finishChunk)));
64
+ controller.enqueue(encoder.encode(builder.createDoneMarker()));
65
+ }
66
+ state.finished = true;
67
+ return;
68
+ }
69
+ // Handle [DONE] marker
70
+ if (event.type === 'done') {
71
+ if (!state.recognized) {
72
+ state.rawSseOut += builder.createDoneMarker();
73
+ controller.enqueue(encoder.encode(state.rawSseOut));
74
+ }
75
+ else {
76
+ controller.enqueue(encoder.encode(builder.createDoneMarker()));
77
+ }
78
+ state.finished = true;
79
+ return;
80
+ }
81
+ // Try to extract content
82
+ const incomingText = extractText(event.data);
83
+ if (incomingText != null) {
84
+ // Switch to normalized mode on first recognized content
85
+ if (!state.recognized) {
86
+ state.recognized = true;
87
+ }
88
+ // Calculate delta (handles both cumulative and incremental)
89
+ const { delta, cumulative } = calculateDelta(incomingText, state.cumulativeText);
90
+ state.cumulativeText = cumulative;
91
+ // Emit content chunk if there's new text
92
+ if (delta) {
93
+ const chunk = builder.createContentChunk(delta);
94
+ controller.enqueue(encoder.encode(builder.formatAsSSE(chunk)));
95
+ }
96
+ }
97
+ else {
98
+ // Unknown payload; keep for pass-through until we recognize format
99
+ if (!state.recognized) {
100
+ state.rawSseOut += `data: ${event.rawData}\n\n`;
101
+ // Prevent memory leak: flush buffer if it exceeds maximum size
102
+ if (state.rawSseOut.length > STREAM_CONFIG.MAX_BUFFER_SIZE) {
103
+ logWarn('SSE buffer exceeded maximum size, flushing to prevent memory leak');
104
+ controller.enqueue(encoder.encode(state.rawSseOut));
105
+ state.rawSseOut = '';
106
+ }
107
+ }
108
+ }
109
+ }
110
+ }
111
+ }
112
+ // If we exit read loop without finishing
113
+ if (!state.finished) {
114
+ if (state.recognized) {
115
+ // Normalized mode: emit finish chunk and [DONE]
116
+ const finishChunk = builder.createFinishChunk('stop');
117
+ controller.enqueue(encoder.encode(builder.formatAsSSE(finishChunk)));
118
+ controller.enqueue(encoder.encode(builder.createDoneMarker()));
119
+ }
120
+ else {
121
+ // Pass-through mode: emit whatever we buffered verbatim
122
+ controller.enqueue(encoder.encode(state.rawSseOut));
123
+ }
124
+ }
125
+ controller.close();
126
+ },
127
+ cancel() {
128
+ reader.cancel();
129
+ },
130
+ });
131
+ // Preserve headers but ensure SSE content type
132
+ const headers = new Headers(response.headers);
133
+ headers.set('content-type', 'text/event-stream; charset=utf-8');
134
+ return new Response(stream, {
135
+ status: response.status,
136
+ statusText: response.statusText,
137
+ headers,
138
+ });
139
+ }
140
+ //# sourceMappingURL=stream-normalizer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stream-normalizer.js","sourceRoot":"","sources":["../../../lib/request/stream-normalizer.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,67 +1,67 @@
1
- {
2
- "name": "opencode-qwen-cli-auth",
3
- "version": "1.0.3",
4
- "description": "Alibaba Qwen OAuth authentication plugin for opencode - use your Qwen account instead of API keys",
5
- "main": "./dist/index.js",
6
- "types": "./dist/index.d.ts",
7
- "type": "module",
8
- "license": "MIT",
9
- "author": "Geoff Hammond",
10
- "repository": {
11
- "type": "git",
12
- "url": "git+https://github.com/TVD-00/opencode-qwen-cli-auth.git"
13
- },
14
- "keywords": [
15
- "opencode",
16
- "qwen",
17
- "alibaba",
18
- "dashscope",
19
- "oauth",
20
- "qwen-cli",
21
- "qwen-coder",
22
- "plugin",
23
- "auth"
24
- ],
25
- "homepage": "https://github.com/TVD-00/opencode-qwen-cli-auth#readme",
26
- "bugs": {
27
- "url": "https://github.com/TVD-00/opencode-qwen-cli-auth/issues"
28
- },
29
- "scripts": {
30
- "build": "tsc && node scripts/copy-fallback.mjs",
31
- "typecheck": "tsc --noEmit",
32
- "test": "vitest run",
33
- "test:watch": "vitest",
34
- "test:ui": "vitest --ui",
35
- "test:coverage": "vitest run --coverage",
36
- "lint": "eslint . --ext .ts",
37
- "lint:fix": "eslint . --ext .ts --fix",
38
- "format": "prettier --write \"**/*.{ts,json,md}\"",
39
- "format:check": "prettier --check \"**/*.{ts,json,md}\""
40
- },
41
- "files": [
42
- "dist/",
43
- "README.md",
44
- "LICENSE"
45
- ],
46
- "engines": {
47
- "node": ">=20.0.0"
48
- },
49
- "peerDependencies": {
50
- "@opencode-ai/plugin": "^0.13.7"
51
- },
52
- "devDependencies": {
53
- "@opencode-ai/plugin": "^0.13.7",
54
- "@opencode-ai/sdk": "^0.13.9",
55
- "@types/node": "^24.6.2",
56
- "@typescript-eslint/eslint-plugin": "^8.0.0",
57
- "@typescript-eslint/parser": "^8.0.0",
58
- "@vitest/ui": "^3.2.4",
59
- "eslint": "^9.0.0",
60
- "prettier": "^3.0.0",
61
- "typescript": "^5.9.3",
62
- "vitest": "^3.2.4"
63
- },
64
- "dependencies": {
65
- "@openauthjs/openauth": "^0.4.3"
66
- }
1
+ {
2
+ "name": "opencode-qwen-cli-auth",
3
+ "version": "1.0.4",
4
+ "description": "Alibaba Qwen OAuth authentication plugin for opencode - use your Qwen account instead of API keys",
5
+ "main": "./dist/index.js",
6
+ "types": "./dist/index.d.ts",
7
+ "type": "module",
8
+ "license": "MIT",
9
+ "author": "Geoff Hammond",
10
+ "repository": {
11
+ "type": "git",
12
+ "url": "git+https://github.com/TVD-00/opencode-qwen-cli-auth.git"
13
+ },
14
+ "keywords": [
15
+ "opencode",
16
+ "qwen",
17
+ "alibaba",
18
+ "dashscope",
19
+ "oauth",
20
+ "qwen-cli",
21
+ "qwen-coder",
22
+ "plugin",
23
+ "auth"
24
+ ],
25
+ "homepage": "https://github.com/TVD-00/opencode-qwen-cli-auth#readme",
26
+ "bugs": {
27
+ "url": "https://github.com/TVD-00/opencode-qwen-cli-auth/issues"
28
+ },
29
+ "scripts": {
30
+ "build": "tsc && node scripts/copy-fallback.mjs",
31
+ "typecheck": "tsc --noEmit",
32
+ "test": "vitest run",
33
+ "test:watch": "vitest",
34
+ "test:ui": "vitest --ui",
35
+ "test:coverage": "vitest run --coverage",
36
+ "lint": "eslint . --ext .ts",
37
+ "lint:fix": "eslint . --ext .ts --fix",
38
+ "format": "prettier --write \"**/*.{ts,json,md}\"",
39
+ "format:check": "prettier --check \"**/*.{ts,json,md}\""
40
+ },
41
+ "files": [
42
+ "dist/",
43
+ "README.md",
44
+ "LICENSE"
45
+ ],
46
+ "engines": {
47
+ "node": ">=20.0.0"
48
+ },
49
+ "peerDependencies": {
50
+ "@opencode-ai/plugin": "^0.13.7"
51
+ },
52
+ "devDependencies": {
53
+ "@opencode-ai/plugin": "^0.13.7",
54
+ "@opencode-ai/sdk": "^0.13.9",
55
+ "@types/node": "^24.6.2",
56
+ "@typescript-eslint/eslint-plugin": "^8.0.0",
57
+ "@typescript-eslint/parser": "^8.0.0",
58
+ "@vitest/ui": "^3.2.4",
59
+ "eslint": "^9.0.0",
60
+ "prettier": "^3.0.0",
61
+ "typescript": "^5.9.3",
62
+ "vitest": "^3.2.4"
63
+ },
64
+ "dependencies": {
65
+ "@openauthjs/openauth": "^0.4.3"
66
+ }
67
67
  }